学习设计模式第二十二 - 观察者模式
本文摘取自TerryLee(李会军)老师的设计模式系列文章,版权归TerryLee,仅供个人学习参考。转载请标明原作者TerryLee。部分示例代码来自DoFactory。
概述
在软件构建过程中,我们需要为某些对象建立一种"通知依赖关系",即一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
UML
图1 Observer模式结构图
参与者
这个模式涉及的类或对象:
-
Subject
-
了解其订阅者。任意数量的订阅者对象可以订阅一个主题
-
提供一个接口用于附加或分离订阅者对象。
-
ConcreteSubject
-
存储ConcreteObserver关注的状态
-
当状态改变时发送通知给其订阅者
-
Observer
-
为订阅对象定义一个更新接口,用于在目标发生变化时进行通知。
-
ConcreteObserver
-
维持一个到ConcreteSubject对象的引用
-
存储需要与subject一直的状态
-
实现Observer中定义的更新接口,以保持自身的状态与subject的状态一致
适用性
观察者模式是GoF23种模式中两个不仅融入.NET Framework类库,同时被.NET 语言本身所支持的模式之一(另一个是迭代器模式)。当编写一个Web应用或Windows应用时你常用到事件及事件处理函数。事件和委托是作为类型级语言特性,分别扮演者观察者模式中Subject与Observers的角色。
订阅者模式促进了松耦合,更容易实现好的面向对象设计。Observer将自己注册到维护一个订阅者列表的Subject对象或由其中解除注册。Subject不依赖于任何特定的observer,只要委托是正确的用于事件的类型。.NET中的event和delegate范式展示了一个观察者模式优雅且强大的实现。
下面是几种使用观察者模式的具体场景:
-
当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
-
当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
-
当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
DoFactory GoF代码
这个标准示例展示了当状态发生变化时,订阅对象被通知并更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | // Observer pattern // Structural example using System; using System.Collections.Generic; namespace DoFactory.GangOfFour.Observer.Structural { // MainApp test application class MainApp { static void Main() { // Configure Observer pattern ConcreteSubject s = new ConcreteSubject(); s.Attach( new ConcreteObserver(s, "X" )); s.Attach( new ConcreteObserver(s, "Y" )); s.Attach( new ConcreteObserver(s, "Z" )); // Change subject and notify observers s.SubjectState = "ABC" ; s.Notify(); // Wait for user Console.ReadKey(); } } // "Subject" abstract class Subject { private List<Observer> _observers = new List<Observer>(); public void Attach(Observer observer) { _observers.Add(observer); } public void Detach(Observer observer) { _observers.Remove(observer); } public void Notify() { foreach (Observer o in _observers) { o.Update(); } } } // "ConcreteSubject" class ConcreteSubject : Subject { private string _subjectState; // Gets or sets subject state public string SubjectState { get { return _subjectState; } set { _subjectState = value; } } } // "Observer" abstract class Observer { public abstract void Update(); } // "ConcreteObserver" class ConcreteObserver : Observer { private string _name; private string _observerState; private ConcreteSubject _subject; // Constructor public ConcreteObserver(ConcreteSubject subject, string name) { this ._subject = subject; this ._name = name; } public override void Update() { _observerState = _subject.SubjectState; Console.WriteLine( "Observer {0}'s new state is {1}" , _name, _observerState); } // Gets or sets subject public ConcreteSubject Subject { get { return _subject; } set { _subject = value; } } } } |
实际使用的示例展示了每当股票价格发生变化时,投资者被通知。
例子中涉及到的类与职责链模式中标准的类对应关系如下:
-
Subject – Stock
-
ConcreteSubject – IBM
-
Observer – IInvestor
-
ConcreteObserver – Investor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | // Observer pattern // Real World example using System; using System.Collections.Generic; namespace DoFactory.GangOfFour.Observer.RealWorld { // MainApp test application class MainApp { static void Main() { // Create IBM stock and attach investors IBM ibm = new IBM( "IBM" , 120.00); ibm.Attach( new Investor( "Sorros" )); ibm.Attach( new Investor( "Berkshire" )); // Fluctuating prices will notify investors ibm.Price = 120.10; ibm.Price = 121.00; ibm.Price = 120.50; ibm.Price = 120.75; // Wait for user Console.ReadKey(); } } // "Subject" abstract class Stock { private string _symbol; private double _price; private List<IInvestor> _investors = new List<IInvestor>(); // Constructor public Stock( string symbol, double price) { this ._symbol = symbol; this ._price = price; } public void Attach(IInvestor investor) { _investors.Add(investor); } public void Detach(IInvestor investor) { _investors.Remove(investor); } public void Notify() { foreach (IInvestor investor in _investors) { investor.Update( this ); } Console.WriteLine( "" ); } // Gets or sets the price public double Price { get { return _price; } set { if (_price != value) { _price = value; Notify(); } } } // Gets the symbol public string Symbol { get { return _symbol; } } } // "ConcreteSubject" class IBM : Stock { // Constructor public IBM( string symbol, double price) : base (symbol, price) { } } // "Observer" interface IInvestor { void Update(Stock stock); } // "ConcreteObserver" class Investor : IInvestor { private string _name; private Stock _stock; // Constructor public Investor( string name) { this ._name = name; } public void Update(Stock stock) { Console.WriteLine( "Notified {0} of {1}'s " + "change to {2:C}" , _name, stock.Symbol, stock.Price); } // Gets or sets the stock public Stock Stock { get { return _stock; } set { _stock = value; } } } } |
.NET优化的代码实现了与上面例子相同的功能但更多的使用了现代的.NET内置的特性。这个例子使用了.NET多播委托来完成,这是观察者模式的一个实现。委托是类型安全的函数指针,其可以用来调用一个方法。泛型委托可以接受事件处理函数指定的参数,换句话说,名为sender的参数不一定非得是object类型,其可以是任意类型(这个例子中是Stock类型)。多播委托由多个方法组成,这些方法以它们被通过C# +=运算符订阅的顺序依次被调用。例子中也使用了.NET3.0中自动属性和类型初始化器等特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | // Observer pattern // .NET Optimized example using System; using System.Collections.Generic; namespace DoFactory.GangOfFour.Observer.NETOptimized { class MainApp { static void Main() { // Create IBM stock and attach investors var ibm = new IBM(120.00); // Attach 'listeners', i.e. Investors ibm.Attach( new Investor { Name = "Sorros" }); ibm.Attach( new Investor { Name = "Berkshire" }); // Fluctuating prices will notify listening investors ibm.Price = 120.10; ibm.Price = 121.00; ibm.Price = 120.50; ibm.Price = 120.75; // Wait for user Console.ReadKey(); } } // Custom event arguments public class ChangeEventArgs : EventArgs { // Gets or sets symbol public string Symbol { get ; set ; } // Gets or sets price public double Price { get ; set ; } } // "Subject" abstract class Stock { protected string _symbol; protected double _price; // Constructor public Stock( string symbol, double price) { this ._symbol = symbol; this ._price = price; } // Event public event EventHandler<ChangeEventArgs> Change; // Invoke the Change event public virtual void OnChange(ChangeEventArgs e) { if (Change != null ) { Change( this , e); } } public void Attach(IInvestor investor) { Change += investor.Update; } public void Detach(IInvestor investor) { Change -= investor.Update; } // Gets or sets the price public double Price { get { return _price; } set { if (_price != value) { _price = value; OnChange( new ChangeEventArgs { Symbol = _symbol, Price = _price }); Console.WriteLine( "" ); } } } } // "ConcreteSubject" class IBM : Stock { // Constructor - symbol for IBM is always same public IBM( double price) : base ( "IBM" , price) { } } // "Observer" interface IInvestor { void Update( object sender, ChangeEventArgs e); } // "ConcreteObserver" class Investor : IInvestor { // Gets or sets the investor name public string Name { get ; set ; } // Gets or sets the stock public Stock Stock { get ; set ; } public void Update( object sender, ChangeEventArgs e) { Console.WriteLine( "Notified {0} of {1}'s " + "change to {2:C}" , Name, e.Symbol, e.Price); } } } |
Observer模式解说
下面通过一个例子来说明Observer模式。监控某一个公司的股票价格变化,可以有多种方式,通知的对象可以是投资者,或者是发送到移动设备,还有电子邮件等。一开始我们先不考虑Observer模式,通过一步步地重构,最终重构为Observer模式。现在有这样两个类:Microsoft和Investor,如下图所示:
图2.发送股票通知例子的类图
它们的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | public class Microsoft { private Investor _investor; private String _symbol; private double _price; public void Update() { _investor.SendData( this ); } public Investor Investor { get { return _investor; } set { _investor = value; } } public String Symbol { get { return _symbol; } set { _symbol = value; } } public double Price { get { return _price; } set { _price = value; } } } public class Investor { private string _name; public Investor( string name) { this ._name = name; } public void SendData(Microsoft ms) { Console.WriteLine( "Notified {0} of {1}'s " + "change to {2:C}" , _name, ms.Symbol, ms.Price); } } |
简单的客户端实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Program { static void Main( string [] args) { Investor investor = new Investor( "Jom" ); Microsoft ms = new Microsoft(); ms.Investor = investor; ms.Symbol = "Microsoft" ; ms.Price = 120.00; ms.Update(); Console.ReadLine(); } } |
运行后结果如下:
Notified Jom of Microsoft's change to ¥120
可以看到,这段代码运行并没有问题,也确实实现了我们最初的设想的功能,把Microsoft的股票价格变化通知到了Jom投资者那儿。但是这里面出现了如下几个问题:
-
Microsoft和Investor之间形成了一种双向的依赖关系,即Microsoft调用了Investor的方法,而Investor调用了Microsoft类的属性。如果有其中一个类变化,有可能会引起另一个的变化。
-
当出现一种的通知对象,比如说是移动设备Mobile:
1 2 3 4 5 6 7 8 9 10 11 12 | public class Mobile { private string _no; public Mobile( string No) { this ._no = No; } public void SendData(Microsoft ms) { Console.WriteLine( "Notified {0} of {1}'s " + "change to {2:C}" , _no, ms.Symbol, ms.Price); } } |
这时候对应的Microsoft的类就应该改变为如下代码,在Microsot类中增加Mobile,同时修改Update()方法使其可以通知到移动设备:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public class Microsoft { private Investor _investor; private Mobile _mobile; private String _symbol; private double _price; public void Update() { _investor.SendData( this ); _mobile.SendData( this ); } public Mobile Mobile { get { return _mobile; } set { _mobile = value; } } public Investor Investor { get { return _investor; } set { _investor = value; } } public String Symbol { get { return _symbol; } set { _symbol = value; } } public double Price { get { return _price; } set { _price = value; } } } |
显然这样的设计极大的违背了"开放-封闭"原则,这不是我们所想要的,仅仅是新增加了一种通知对象,就需要对原有的Microsoft类进行修改,这样的设计是很糟糕的。对此做进一步的抽象,既然出现了多个通知对象,我们就为这些对象之间抽象出一个接口,用它来取消Microsoft和具体的通知对象之间依赖。
图3.将例子中通知功能抽象为一个接口
实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | public interface IObserver { void SendData(Microsoft ms); } public class Investor : IObserver { private string _name; public Investor( string name) { this ._name = name; } public void SendData(Microsoft ms) { Console.WriteLine( "Notified {0} of {1}'s " + "change to {2:C}" , _name, ms.Symbol, ms.Price); } } public class Microsoft { private IObserver _investor; private String _symbol; private double _price; public void Update() { _investor.SendData( this ); } public String Symbol { get { return _symbol; } set { _symbol = value; } } public double Price { get { return _price; } set { _price = value; } } public IObserver Investor { get { return _investor; } set { _investor = value; } } } |
做到这一步,可以看到,我们在降低两者的依赖性上已经迈进了一小步,正在朝着弱依赖性这个方向变化。在Microsoft类中已经不再依赖于具体的Investor,而是依赖于接口IObserver。
但同时我们看到,再新出现一个移动设备这样的通知对象,Microsoft类仍然需要改变,对此我们再做如下重构,在Microsoft中维护一个IObserver列表,同时提供相应的维护方法。
图4.解除依赖的系统的类图
Microsoft类的实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public class Microsoft { private List<IObserver> observers = new List<IObserver>(); private String _symbol; private double _price; public void Update() { foreach (IObserver ob in observers) { ob.SendData( this ); } } public void AddObserver(IObserver observer) { observers.Add(observer); } public void RemoveObserver(IObserver observer) { observers.Remove(observer); } public String Symbol { get { return _symbol; } set { _symbol = value; } } public double Price { get { return _price; } set { _price = value; } } } |
此时客户端的调用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Program { static void Main( string [] args) { IObserver investor1 = new Investor( "Jom" ); IObserver investor2 = new Investor( "TerryLee" ); Microsoft ms = new Microsoft(); ms.Symbol = "Microsoft" ; ms.Price = 120.00; ms.AddObserver(investor1); ms.AddObserver(investor2); ms.Update(); Console.ReadLine(); } } |
走到这一步,已经有了Observer模式的影子了,Microsoft类不再依赖于具体的Investor,而是依赖于抽象的IOberver。存在着的一个问题是Investor仍然依赖于具体的公司Microsoft,况且公司还会有很多IBM,Google等,解决这样的问题很简单,只需要再对Microsoft类做一次抽象。如下图所示:
图5.例子实现观察者模式后的类图
实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | public abstract class Stock { private List<IObserver> observers = new List<IObserver>(); private String _symbol; private double _price; public Stock(String symbol, double price) { this ._symbol = symbol; this ._price = price; } public void Update() { foreach (IObserver ob in observers) { ob.SendData( this ); } } public void AddObserver(IObserver observer) { observers.Add(observer); } public void RemoveObserver(IObserver observer) { observers.Remove(observer); } public String Symbol { get { return _symbol; } } public double Price { get { return _price; } } } public class Microsoft : Stock { public Microsoft(String symbol, double price) : base (symbol, price) { } } public interface IObserver { void SendData(Stock stock); } public class Investor : IObserver { private string _name; public Investor( string name) { this ._name = name; } public void SendData(Stock stock) { Console.WriteLine( "Notified {0} of {1}'s " + "change to {2:C}" , _name, stock.Symbol, stock.Price); } } |
客户端程序代码如下:
1 2 3 4 5 6 7 8 9 10 11 | class Program { static void Main( string [] args) { Stock ms = new Microsoft( "Microsoft" , 120.00); ms.AddObserver( new Investor( "Jom" )); ms.AddObserver( new Investor( "TerryLee" )); ms.Update(); Console.ReadLine(); } } |
到这里我们可以看到,通过不断的重构,不断地抽象,我们由一开始的很糟糕的设计,逐渐重构为使用Observer模式的这样一个方案。在这个例子里面,IOberser充当了观察者的角色,而Stock则扮演了主题对象角色,在任何时候,只要调用了Stock的Update()方法,它就会通知它的所有观察者对象。同时可以看到,通过Observer模式,取消了直接依赖,变为间接依赖,这样大大提供了系统的可维护性和可扩展性。
推模式与拉模式
对于发布-订阅模型,大家都很容易能想到推模式与拉模式,用SQL Server做过数据库复制的朋友对这一点很清楚。在Observer模式中同样区分推模式和拉模式,我先简单的解释一下两者的区别:推模式是当有消息时,把消息信息以参数的形式传递(推)给所有观察者,而拉模式是当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息。知道了这一点,大家可能很容易发现上面我所举的例子其实是一种推模式的Observer模式。我们先看看这种模式带来了什么好处:当有消息时,所有的观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。但是它也有缺陷,第一是所有的观察者得到的消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能"按需所取";第二,当通知消息的参数有变化时,所有的观察者对象都要变化。鉴于以上问题,拉模式就应运而生了,它是由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。OK,说到这儿,你是否对于推模式和拉模式有了一点了解呢?我把前面的例子修改为了拉模式,供大家参考,可以看到通知方法是没有任何参数的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | public abstract class Stock { private List<IObserver> observers = new List<IObserver>(); private String _symbol; private double _price; public Stock(String symbol, double price) { this ._symbol = symbol; this ._price = price; } public void Update() { foreach (IObserver ob in observers) { ob.SendData(); } } public void AddObserver(IObserver observer) { observers.Add(observer); } public void RemoveObserver(IObserver observer) { observers.Remove(observer); } public String Symbol { get { return _symbol; } } public double Price { get { return _price; } } } public class Microsoft : Stock { public Microsoft(String symbol, double price) : base (symbol, price) { } } public interface IObserver { void SendData(); } public class Investor : IObserver { private string _name; private Stock _stock; public Investor( string name, Stock stock) { this ._name = name; this ._stock = stock; } public void SendData() { Console.WriteLine( "Notified {0} of {1}'s " + "change to {2:C}" , _name, _stock.Symbol, _stock.Price); } } class Program { static void Main( string [] args) { Stock ms = new Microsoft( "Microsoft" , 120.00); ms.AddObserver( new Investor( "Jom" , ms)); ms.AddObserver( new Investor( "TerryLee" , ms)); ms.Update(); Console.ReadLine(); } } |
当然拉模式也是有一些缺点的,主体对象和观察者之间的耦合加强了,但是这可以通过抽象的手段使这种耦合关系减到最小。
来自《深入浅出设计模式》的例子
这个例子中,实现了当气象站更新数据时,相关的气象板接收通知并更新的功能。通过使用观察者模式使两者之间实现了松耦合。这个例子没有使用C#语言与.NET框架对观察者模式的内在支持。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | using System; using System.Collections.Generic; namespace DoFactory.HeadFirst.Observer.WeatherStation { class WeatherStationHeatIndex { static void Main( string [] args) { var weatherData = new WeatherData(); var currentDisplay = new CurrentConditionsDisplay(weatherData); var statisticsDisplay = new StatisticsDisplay(weatherData); var forecastDisplay = new ForecastDisplay(weatherData); var heatIndexDisplay = new HeatIndexDisplay(weatherData); weatherData.SetMeasurements(80, 65, 30.4f); weatherData.SetMeasurements(82, 70, 29.2f); weatherData.SetMeasurements(78, 90, 29.2f); // Wait for user Console.ReadKey(); } } #region Subject public interface ISubject { void RegisterObserver(IObserver observer); void RemoveObserver(IObserver observer); void NotifyObservers(); } public class WeatherData : ISubject { private List<IObserver> _observers = new List<IObserver>(); private float _temperature; private float _humidity; private float _pressure; public void RegisterObserver(IObserver observer) { _observers.Add(observer); } public void RemoveObserver(IObserver observer) { _observers.Remove(observer); } public void NotifyObservers() { foreach (IObserver observer in _observers) { observer.Update(_temperature, _humidity, _pressure); } } public void MeasurementsChanged() { NotifyObservers(); } public void SetMeasurements( float temperature, float humidity, float pressure) { this ._temperature = temperature; this ._humidity = humidity; this ._pressure = pressure; MeasurementsChanged(); } } #endregion #region Observer public interface IObserver { void Update( float temperature, float humidity, float pressure); } public interface IDisplayElement { void Display(); } public class CurrentConditionsDisplay : IObserver, IDisplayElement { private float _temperature; private float _humidity; private ISubject _weatherData; public CurrentConditionsDisplay(ISubject weatherData) { this ._weatherData = weatherData; weatherData.RegisterObserver( this ); } public void Update( float temperature, float humidity, float pressure) { this ._temperature = temperature; this ._humidity = humidity; Display(); } public void Display() { Console.WriteLine( "Current conditions: " + _temperature + "F degrees and " + _humidity + "% humidity" ); } } public class ForecastDisplay : IObserver, IDisplayElement { private float _currentPressure = 29.92f; private float _lastPressure; private WeatherData _weatherData; public ForecastDisplay(WeatherData weatherData) { this ._weatherData = weatherData; weatherData.RegisterObserver( this ); } public void Update( float temperature, float humidity, float pressure) { _lastPressure = _currentPressure; _currentPressure = pressure; Display(); } public void Display() { Console.Write( "Forecast: " ); if (_currentPressure > _lastPressure) { Console.WriteLine( "Improving weather on the way!" ); } else if (_currentPressure == _lastPressure) { Console.WriteLine( "More of the same" ); } else if (_currentPressure < _lastPressure) { Console.WriteLine( "Watch out for cooler, rainy weather" ); } } } public class HeatIndexDisplay : IObserver, IDisplayElement { private float _heatIndex = 0.0f; private WeatherData _weatherData; public HeatIndexDisplay(WeatherData weatherData) { this ._weatherData = weatherData; weatherData.RegisterObserver( this ); } public void Update( float temperature, float humidity, float pressure) { _heatIndex = ComputeHeatIndex(temperature, humidity); Display(); } private float ComputeHeatIndex( float t, float rh) { float heatindex = ( float ) ( (16.923 + (0.185212 * t)) + (5.37941 * rh) - (0.100254 * t * rh) + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) + (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) + (0.000000000843296 * (t * t * rh * rh * rh)) - (0.0000000000481975 * (t * t * t * rh * rh * rh))); return heatindex; } public void Display() { Console.WriteLine( "Heat index is " + _heatIndex + "\n" ); } } public class StatisticsDisplay : IObserver, IDisplayElement { private float _maxTemp = 0.0f; private float _minTemp = 200; private float _tempSum = 0.0f; private int _numReadings; private WeatherData _weatherData; public StatisticsDisplay(WeatherData weatherData) { this ._weatherData = weatherData; weatherData.RegisterObserver( this ); } public void Update( float temperature, float humidity, float pressure) { _tempSum += temperature; _numReadings++; if (temperature > _maxTemp) { _maxTemp = temperature; } if (temperature < _minTemp) { _minTemp = temperature; } Display(); } public void Display() { Console.WriteLine( "Avg/Max/Min temperature = " + (_tempSum / _numReadings) + "/" + _maxTemp + "/" + _minTemp); } } #endregion } |
.NET中的Observer模式
正如上文提到的,.NET事件模型使用观察者模式来实现,并且贯穿整个.NET Framwork – 包括.NET语言和.NET类库。
在.NET中,相信大家对于事件和委托都已经不陌生了,这里就不具体多说了。利用事件和委托来实现Observer模式我认为更加的简单和优雅,也是一种更好的解决方案。因为在上面的示例中我们可以看到,虽然取消了直接耦合,但是又引入了不必要的约束(暂且这么说吧)。即那些子类必须都继承于主题父类,还有观察者接口等。网上有很多这方面的例子,上面的例子简单的用事件和委托实现如下,仅供大家参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | class Program { static void Main( string [] args) { Stock stock = new Stock( "Microsoft" , 120.00); Investor investor = new Investor( "Jom" ); stock.NotifyEvent += new NotifyEventHandler(investor.SendData); stock.Update(); Console.ReadLine(); } } public delegate void NotifyEventHandler( object sender); public class Stock { public NotifyEventHandler NotifyEvent; private String _symbol; private double _price; public Stock(String symbol, double price) { this ._symbol = symbol; this ._price = price; } public void Update() { OnNotifyChange(); } public void OnNotifyChange() { if (NotifyEvent != null ) { NotifyEvent( this ); } } public String Symbol { get { return _symbol; } } public double Price { get { return _price; } } } public class Investor { private string _name; public Investor( string name) { this ._name = name; } public void SendData( object obj) { if (obj is Stock) { Stock stock = (Stock)obj; Console.WriteLine( "Notified {0} of {1}'s " + "change to {2:C}" , _name, stock.Symbol, stock.Price); } } } |
效果及实现要点
-
使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。
-
目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知。目标对象对此一无所知。
-
在C#中的Event。委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象,委托是比抽象Observer接口更为松耦合的设计。
总结
通过Observer模式,把一对多对象之间的通知依赖关系的变得更为松散,大大地提高了程序的可维护性和可扩展性,也很好的符合了开放-封闭原则。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异