一、概述:
DP一书对Observer模式意图的描述是:“定义对象间的一种一对多依赖关系,使得每当一个对象改变装态,则其相关依赖对象皆得到通知并自动更新。”
在软件的构建过程中,我们常常要为一些对象建立一种通知依赖关系:当一个对象(Subject)的状态发生改变或某一特定事件发生时,所有的依赖对象(Observer)都需要得到通知。由于需求的变化,需要得到通知的Observer对象可能会发生变化,这是我们就需要使用面向对象的设计方法封装这种变化,使观察者和被观察者形成一种松散的耦合关系。
二、分析问题并寻求解决方案:
根据面向对象的分析原则,我们应该分析问题中的变化因素并尝试封装之。我们发现“变化”主要来自两个方面
1)不同类型的对象:要获得通知的对象往往不属于相同的类型。
2)不同的接口:由于所属类型不同,往往导致出现不同的接口。
如果所有的观察者没有相同的接口,我们就必须修改目标对象(Subject)以通知各种不同类型的观察者(Observer)。这样做显然会导致目标对象的复杂化,而且会增加观察者和被观察者的耦合度。
为了统一观察者的类型,可以使用接口来封装不同的观察者。同时根据对象要对自己负责的原则,观察者(Observer)应该负责了解自己的观察目标(Subject)是什么,而目标对象(Subject)无需知道那些具体的观察者(Observer)依赖于自己。这就需要在目标对象中添加注册观察者的方法:
1)Attach(Observer):将给定的Observer对象注册到目标对象的观察者列表。
2)Detach(Observer):从目标对象的观察者列表中删除指定的Observer对象。
这样,由于目标对象(Subject)持有观察者(Object)的对象列表,当目标对象状态改变或特定事件发生时,就可以轻松地通知各种类型的观察者。我们可以在Subject中实现一个Notify方法遍历观察者列表,并调用每个Observer对象的Update方法(包含了相应的事件处理代码)。
三、观察者模式的UML类图:
图1:观察者模式的UML类图
四、示例——股票和投资者
假定我们开放一个简单的应用程序来跟踪股票的价格。我们指定一个Stock类来模拟股票交易市场上的股票,一个Investor类模拟各种不同的投资者。随着时间的变化股票的价格会发生变化,这种变化应该以Email、手机或其它方式通知所有已经注册的投资者。
(1)不考虑设计模式的实现
2 public class Stock
3 {
4 private InvestorWithEmail investorWithEmail;
5 private InvestorWithCell investorWithCell;
6
7 private string symbol;
8 private string price;
9
10 public Stock(string symbol, string price)
11 {
12 this.symbol = symbol;
13 this.price = price;
14 }
15
16 public string Symbol
17 {
18 get { return symbol; }
19 set { symbol = value; }
20 }
21 public string Price
22 {
23 get { return price; }
24 set { price = value; }
25 }
26
27 public InvestorWithEmail InvestorWithEmail
28 {
29 get { return investorWithEmail; }
30 set { investorWithEmail = value; }
31 }
32 public InvestorWithCell InvestorWithCell
33 {
34 get { return investorWithCell; }
35 set { investorWithCell = value; }
36 }
37
38 public void Update(Stock stock)
39 {
40 //.
41 investorWithEmail.SendEmail(stock);
42 investorWithCell.SendSM(stock);
43 }
44
45
46 }
47
48 //观察者:需要以Email方式接受通知的投资者
49 public class InvestorWithEmail
50 {
51 private string investorName;
52
53 public InvestorWithEmail(string investorName)
54 {
55 this.investorName = investorName;
56 }
57
58 public void SendEmail(Stock stock)
59 {
60 Console.WriteLine("Notify:{0},The price of {1} was changed to:{2}!", stock.InvestorWithEmail.investorName, stock.Symbol, stock.Price);
61 }
62 }
63 //观察者:需要以Cell方式接受通知的投资者
64 public class InvestorWithCell
65 {
66 private string investorName;
67
68 public InvestorWithCell(string investorName)
69 {
70 this.investorName = investorName;
71 }
72
73 public void SendSM(Stock stock)
74 {
75 Console.WriteLine("Notify:{0},The price of {1} was changed to:{2}!", stock.InvestorWithCell.investorName, stock.Symbol, stock.Price);
76 }
77 }
78
79 //简单的客户端实现
80 public class StcokApp
81 {
82 public static void Main()
83 {
84 //创建观察者:不同的投资者
85 InvestorWithEmail investorWithEmail = new InvestorWithEmail("Mike");
86 InvestorWithCell investorWithCell = new InvestorWithCell("Tom");
87
88 //创建被观察者:股票
89 Stock stock = new Stock("MS", "100");
90
91 stock.InvestorWithEmail = investorWithEmail;
92 stock.InvestorWithCell = investorWithCell;
93 stock.Update(stock);
94
95 Console.ReadKey();
96
97 }
98
99
100 }
运行结果:
代码实现了我们设想的功能,把MS股票价格的变动通知到了不同的投资者Mike和Tom。但是我们经过细心研究,这里面仍然存在着几个问题:
1)股票跟不同的投资者之间存在着一种强依赖关系。Stock需要调用InvetorWithEmail和InvestorWithCell的方法,如果InvetorWithEmail和InvestorWithCell发生变化可能导致Stock发生变化。
2)如果出现新的通知方式,将不得不修改Stock类以适应新的需求。这违背了开放-封闭原则(软件实体应该是可以扩展的,但是不可以修改)。
(2)如何解决存在的问题呢?根据我们在二中的分析,演化出如下的解决方案:
图2:股票和投资者的类图
这时具体股票和具体投资者的直接依赖关系变成了间接依赖关系,并且通过这种方式我们可以在不影响现有类的情况下,添加新的股票和投资者类型,保持了类间的较松耦合。
具体实现代码如下:
2 public abstract class Stock
3 {
4 List<IInvestor> investorList = new List<IInvestor>();
5
6 private string symbol;
7 private string price;
8
9 public Stock(string symbol, string price)
10 {
11 this.symbol = symbol;
12 this.price = price;
13 }
14
15 public string Symbol
16 {
17 get { return symbol; }
18 set { symbol = value; }
19 }
20 public string Price
21 {
22 get { return price; }
23 set { price = value; }
24 }
25
26 public IInvestor IInvestor
27 {
28 get
29 {
30 throw new System.NotImplementedException();
31 }
32 set
33 {
34 }
35 }
36
37 //附加投资者
38 public void Attach(IInvestor investor)
39 {
40 investorList.Add(investor);
41 }
42 //移除投资者
43 public void Detach(IInvestor investor)
44 {
45 investorList.Remove(investor);
46 }
47 public void Notify(Stock stock)
48 {
49 //.
50 foreach (IInvestor investor in investorList)
51 {
52 investor.Update(this);
53 }
54 }
55
56
57 }
58 //具体的被观察者:股票
59 class StockofMS : Stock
60 {
61 public StockofMS(string symbol, string price)
62 : base(symbol, price)
63 {
64 }
65 }
66
67 //观察者
68 public interface IInvestor
69 {
70 void Update(Stock stock);
71 }
72 //具体的观察者:需要以Email方式接受通知的投资者
73 public class InvestorWithEmail : IInvestor
74 {
75 private string investorName;
76
77 public InvestorWithEmail(string investorName)
78 {
79 this.investorName = investorName;
80 }
81
82 public void Update(Stock stock)
83 {
84 Console.WriteLine("Notify:{0},The price of {1} was changed to:{2}!",this.investorName, stock.Symbol, stock.Price);
85 }
86 }
87 //具体的观察者:需要以Cell方式接受通知的投资者
88 public class InvestorWithCell : IInvestor
89 {
90 private string investorName;
91
92 public InvestorWithCell(string investorName)
93 {
94 this.investorName = investorName;
95 }
96
97 public void Update(Stock stock)
98 {
99 Console.WriteLine("Notify:{0},The price of {1} was changed to:{2}!", this.investorName, stock.Symbol, stock.Price);
100 }
101 }
102
103 //简单的客户端实现
104 public class StcokApp
105 {
106 public static void Main()
107 {
108 //创建观察者:不同的投资者
109 InvestorWithEmail investorWithEmail = new InvestorWithEmail("Mike");
110 InvestorWithCell investorWithCell = new InvestorWithCell("Tom");
111
112 //创建被观察者:股票
113 Stock stock = new StockofMS("MS", "100");
114
115 //附加不同类型的观察者
116 stock.Attach(investorWithEmail);
117 stock.Attach(investorWithCell);
118
119 stock.Price = "119";
120
121 stock.Notify(stock);
122
123 Console.ReadKey();
124
125 }
126
127
128 }
结论:
观察者模式了被观察对象和观察者对象的连接,提供了广播式的对象间通信,并且容易增加/移除观察者对象。当需要得到某事件通知的Observer对象列表时变化的或者一个对象需要通知其它对象而又不需要掌握其它对象的详细信息时使用Observer模式十分合适。