例如,假定我们要开发一种简单的应用程序,来跟踪全天的股票价格。在此应用程序中,我们指定一个Stock类来模拟在NASDAQ交易的各种股票。该类包含一个实例变量,它表示在全天不同时段经常波动的股价。为了向用户显示此信息,应用程序使用一个StockDisplay类向stdout(标准输出)写信息。在此应用程序中,一个Stock类实例作为主体,一个StockDisplay类实例作为观察者。随着股价在交易日中随时间发生变化,Stock实例的当前股价也会发生变化(它怎样变化并不重要)。因为StockDisplay实例正在观察Stock实例,所以在这些状态发生变化(修改股价)时,就会向用户显示这些变化。
通过使用这种观察过程,可以在Stock和StockDisplay类之间划分界限。假定应用程序的要求第二天发生变化,要使用基于窗体的用户界面。要启用此新功能,只需要构造一个新类StockForm作为观察者。无论发生什么情况,Stock类都不需要进行任何修改。事实上,它甚至不知道发生此类更改。类似地,如果需求变化要求Stock类从另一个
物理模型
正如大多数解决方案一样,问题在于细节。Observer模式也不例外。虽然逻辑模型规定观察者观察主体;但在实现这种模式时,这实际上是一个名称误用。更准确地说,观察者向主体注册,表明它观察主体的意愿。在某种状态发生变化时,主体向观察者通知这种变化情况。当观察者不再希望观察主体时,观察者向主体撤消注册。这些步骤分别称为观察者注册、通知和撤消注册。
大多数框架通过回调来实现注册和通知。图2、3和4中所示的UML序列图模拟这种方法通常使用的对象和方法调用。对于不熟悉序列图的人来说,最上面的矩形框表示对象,而箭头表示方法调用。
图2描述了注册序列。观察者对主体调用Register方法,以将其自身作为参数传递。在主体收到此引用后,它必须将其存储起来,以便在将来某个时间状态发生变化时通知观察者。大多数观察者实现并非将观察者引用直接存储在实例变量中,而是将此任务委托给一个单独的对象(通常为一个容器)。使用容器来存储观察者实例可提供非常大的好处,我们将对它进行简要介绍。
图3突出显示了通知序列。当状态发生变化时(AskPriceChanged),主体通过调用Get观察者s方法来检索容器中的所有观察者。主体然后枚举检索的观察者,并调用Notify方法以通知观察者所发生的状态变化。
图4显示撤消注册序列。此序列是在观察者不再需要观察主体时执行的。观察者调用UnRegister方法,并将其自身作为参数进行传递。然后,主体对容器调用Remove方法以结束观察过程。
回到我们的股票应用程序,让我们分析一下注册和通知过程所产生的影响。在应用程序启动过程中,一个StockDisplay类实例注册到Stock实例中,并将其自身作为参数传递到Register方法。Stock实例(在容器中)保存对StockDisplay实例的引用。当股价属性发生变化时,Stock实例通过调用Notify方法向StockDisplay通知所发生的变化。在应用程序关闭时,StockDisplay实例使用以下方法撤消注册Stock实例:调用UnRegister方法,终止两个实例之间的关系。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/webkaifa/)请注意利用容器(而不是使用实例变量)来存储观察者引用有什么优点。假定除当前用户接口StockDisplay外,我们还需要绘制股价在交易日内变化的实时图形。为此,我们创建了一个名为StockGraph的新类,它绘制股价(y轴)和当天时间(x轴)的图形。在应用程序启动时,它同时在Stock实例中注册StockDisplay和StockGraph类的实例。因为主体在容器(与实例变量相对)中存储观察者,所以这不会出现问题。当股价发生变化时,Stock实例向其容器中的两个观察者实例通知所发生的状态变化。正如我们所看到的一样,使用容器可提供更大的灵活性,即每个主体可支持多个观察者。这使主体有可能向无数多个观察者通知所发生的状态变化,而不是只通知一个观察者。
虽然不是强制要求,但很多框架为观察者和主体提供了一组要实现的接口。正如下面的C#代码示例所示,IObserver接口公开一种公共方法Notify。此接口是由所有要用作观察者的类实现的。IObservable接口(是由所有要用作主体的类实现的)公开两种方法Register和UnRegister。这些接口通常采用抽象虚拟类或真实接口的形式(如果实现语言支持此类构造的话)。利用这些接口有助于减少观察者和主体之间的耦合关系。与观察者和主体类之间的紧密耦合关系不同,IObserver和IObservable接口允许执行独立于实现的操作。通过对接口的分析,您将注意到键入的所有方法针对的是接口类型(与具体类相对)。这种方法将接口编程模型的优点扩展到Observer模式。
IObserver和IObservable接口(C#)
//interface the all observer classes should implementpublic interface IObserver {void Notify(object anObject);}//IObserver//interface that all observable classes should implementpublic interface IObservable {void Register(IObserver anObserver);void UnRegister(IObserver anObserver);}//IObservable
再回到我们的示例应用程序,我们知道Stock类用作主体。因此,它将实现IObservable接口。类似地,StockDisplay类实现IObserver接口。因为所有操作都是由该接口定义的(而不是由具体类定义的),所以Stock类并未与StockDisplay类绑定在一起,反之亦然。这使我们能够快速地更改特定的观察者或主体实现,而不会影响应用程序的其他部分(使用不同的观察者替换StockDisplay或添加额外的观察者实例)。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/webkaifa/)除了这些接口外,框架还经常为主体提供一个通用基类,减少了支持Observer模式所需的工作。基类实现IObservable接口,以提供支持观察者实例存储和通知所需的基础结构。下面的C#代码示例简要介绍一个名为ObservableImpl的基类。尽管可能任何容器都可以完成这一任务,但该类在Register和UnRegister方法中将观察者存储委托给哈希表实例(为了方便起见,我们在示例中使用哈希表作为容器,它只使用一个方法调用来撤消注册特定的观察者实例)。还要注意添加了NotifyObservers方法。此方法用于通知哈希表中存储的观察者。在调用此方法时,将枚举该容器,并对观察者实例调用Notify方法。
ObservableImpl类(C#)
//helper class that implements observable interfacepublic class ObservableImpl:IObservable {//container to store the observer instance (is not synchronized forthis example)protected Hashtable _ObserverContainer=new Hashtable();//add the observerpublic void Register(IObserver anObserver){_ObserverContainer.Add(anObserver,anObserver);}//Register//remove the observerpublic void UnRegister(IObserver anObserver){_ObserverContainer.Remove(anObserver);}//UnRegister//common method to notify all the observerspublic void NotifyObservers(object anObject) {//enumeration the observers and invoke their notify methodforeach(IObserver anObserver in _ObserverContainer.Keys) {anObserver.Notify(anObject);}//foreach}//NotifyObservers}//ObservableImpl
我们的示例应用程序使用以下方法来利用此基类基础结构:修改Stock类以扩展ObservableImpl类,而不是提供其自己的特定IObservable接口实现。因为ObservableImpl类实现了IObservable接口,所以不需要对StockDisplay类进行任何更改。实际上,这种方法简化了Observer模式的实现,在保持类之间松散耦合关系的同时,使多个主体重复使用相同的功能。
下面的.NET观察者示例重点说明了IObservable和IObserver接口以及ObservableBase类在我们的股票应用程序中的使用情况。除了Stock和StockDisplay类外,此示例使用MainClass将观察者和主体实例关联起来,并修改Stock实例的AskPrice属性。此属性负责调用基类的NotifyObservers方法,而该方法又向该实例通知相关的状态变化。
观察者示例(C#)
//represents a stock in an applicationpublic class Stock:ObservableImpl {//instance variable for ask priceobject _askPrice;//property for ask pricepublic object AskPrice {set { _askPrice=value;base.NotifyObservers(_askPrice);}//set}//AskPrice property}//Stock//represents the user interface in the applicationpublic class StockDisplay:IObserver {public void Notify(object anObject){Console.WriteLine("The new ask price is:" + anObject);}//Notify}//StockDisplaypublic class MainClass{public static void Main() {//create new display and stock instancesStockDisplay stockDisplay=new StockDisplay();Stock stock=new Stock();//register the gridstock.Register(stockDisplay);//loop 100 times and modify the ask pricefor(int looper=0;looper 100;looper++) {stock.AskPrice=looper;}//unregister the displaystock.UnRegister(stockDisplay);}//Main}//MainClass
.NET框架中的Observer模式
基于我们对Observer模式的了解,让我们将注意力转向此模式在.NET框架中的使用情况。您们当中非常熟悉FCL中所公开类型的人将会注意到,框架中没有IObserver、IObservable或ObservableImpl类型。虽然您的确可以在.NET应用程序中使用这些构造,但引入委托和事件可提供新的、功能强大的方法来实现Observer模式,而不必开发专用于支持该模式的特定类型。事实上,因为委托和事件是CLR的一级成员,所以将此模式的基本构造添加到.NET框架的核心中。因此,FCL在其结构中广泛使用Observer模式。
介绍委托和事件内部工作方式的文章非常多,我们在此不再赘述。我们只需说明委托是面向对象(和类型安全)版的函数指针就够了。委托实例保存对实例或类方法的引用,允许匿名调用绑定方法。事件是在类上声明的特殊构造,可在运行时发布被关注对象的状态变化。事件表示我们前面用于实现Observer模式的注册、撤消注册和通知方法的形式抽象(CLR和多种不同的编译器对它提供支持)。委托是在运行时注册到特定事件中的。在引发事件时,将调用所有注册的委托,以使它们能够收到事件的通知。