文章目录
观察者(Observer)模式
隶属类别——对象行为型模式
1. 意图
定义了对象之间的一对多依赖,,当一个对象(主题Subject对象)的状态发生改变时,所以依赖于它的对象都收到通知并被自动更新
2. 别名
依赖(Dependents),发布-订阅(Publish-Subscribe)
3. 动机
将一个系统分割成一系列互相协作的类有一个常见的副作用:需要维护相关对象间的一致性。我们不希望为了维持一致性而使各个类紧密耦合,因为这样降低了它们的可重用性。
例如:许多图形用户界面工具箱将用户应用的界面表示与底下的应用数据分离[KP88,LVC89,P+88,WGM88]。定义应用数据的类和负责界面表示的类可以各自独立地复用。当然它们也可以一起工作。一个表格对象和一个柱状图对象可使用不同的表示形式描述同有个应用数据对象的信息。表格对象和柱状图对象和折线图对象互相并不知道对方的存在,这样使你可以根据需要单独复用表格和柱状图和折线图。
这一行为意味着表格对象和柱状图对象和折线图对象都依赖于数据对象,因此数据对象的任何状态改变都能立即通知他们。同时也没有理由将依赖于该数据对象的数目限定为三个,对相同的数据可以有任意数目的不同用户界面。
Observer模式描述了如何建立这种关系。这一模式中的关键对象是目标(subject)和观察者(observer).一个目标可以有任意数目的依赖它的观察者。一旦目标的状态发生改变,所有的观察者都得到通知。作为对这个通知的响应,每个观察者都将查询目标以使其状态与目标的状态同步。
这种交互也称为发布-订阅(pulish-subcreibe).目标是通知的发布者。它发出通知时并不需要知道谁是它的观察者。可以有任意数目的观察者订阅并接受通知。
4. 适用性
在以下任意情况下可以使用观察者模式:
- 当一个抽象模型有两个方面,其中一个方面一来来于另一方面。将这两者封装在独立的对象中以使他们可以独立地改变和复用。
- 当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。
- 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧密耦合的。
5. 结构
6. 参与者
- Subject(主题或者称为目标)
- 目标知道它的观察者。可以有任意多个观察者观察同一个目标。
- 提供注册和删除观察者对象的接口。
- Observer(观察者)
- 为那些在主题发成改变时需获取通知的对象定义一个更好接口。
- ConcreteSubject(具体主题)
- 将有关状态存入个ConcreteObserver对象。
- 当它的状态发生改变时,向它的各个观察者发生通知。
- ConcreteObserver(具体观察者)
- 维护一个指向ConcreSubject对象的引用。
- 存储有关的状态,这些状态与目标的状态保持一致。
- 实现Observer的更新接口以使自身状态和目标状态保持一致。
7. 协作
- 当ConcreteSubject发生任何可能导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者。
- 在得到一个具体主题的改变通知后,ConcreteObserver对象可向目标对象查询信息,ConcreteObserver使用这些信息以使它的状态与主题对象的状态一致
- 注意发生改变请求的Observer对象并一定是立即更新,而是将其推迟到它从目标得到一个通知之后。notifyObserver()不总是由目标对象调用,它也可被一个观察者或者其他对象调用。实现一节将讨论一些常用的变化。
8. 效果
Observer模式允许你独立的改变目标和观察者。你可以单独复用目标对象而无需同时复用其观察者,反之亦然。它也使你可以在不改动目标和其他的观察者的前提下增加观察者。
下面是观察者(Observer)模式的优缺点:
优点:
- 1.目标和观察者之间抽象耦合 一个目标所知道的仅仅是它有一系列观察者,每个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。因为目标和观察者不是紧密耦合的,它们可以属于一个系统中的不同层次。一个处于较低层次的目标可与一个处于较高层次的观察者通信并通知它,这样就保持了系统层次的完整。如果目标和观察者混在一块,那么得到的对象要么横贯两个层次(违反了层次性),要么必须放在这两层的某一层中(这可能会损害层次抽象)。
- 2.支持广播通信 不像通常的请求,目标发送的通知不需指定它的接受者。通知被自动广播给所有已向该目标对象登记的有关对象。目标对象并不关系到底有多少对象对自己感兴趣;它唯一的责任就是通知它的观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。
缺点
- 1.意外的更新 因为一个观察者并不知道其它观察者的存在,它可能对改变目标的最终代价一无所知。在目标上一个看似无害的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外,如果依赖准则的定义不当或维护不当,常常会引起错误的更新,这种错误通常很难捕捉。简单的更新协议不提供具体细节说明目标中什么被改变了,这就使得上述问题更加严重,如果没有其他协议帮助观察者发现说明发生了改变,它们可能会被迫尽力减少改变。
9. 实现
这一节讨论一些与实现依赖机制相关的问题
-
1.创建目标到其观察者之间的映射 一个目标对象跟踪它应通知的观察者的最简单的方法就是显式地在目标中保存它们的引用。然而,当目标很多而观察者较少时,这样存储可能代价太高。一个解决办法是用时间换空间,用一个关联查找机制(例如一个HashMap)来维护目标到观察者的映射。这样一个没有观察的目标就不产生存储开销。但另一方面,这一方法增加了访问观察者的开销
-
2.观察多个对象 在某些情况下,一个观察者依赖于多个目标可能是有意义的。例如,一个表格对象可能依赖于多个数据源。在这种情况下,必须扩展update方法的入口比如update(Subject subject)以使观察者知道是哪个目标用来的通知。目标对象可以简单地将自己作为update()方法的参数,让观察这知道是哪一个目标
-
3.谁来触发更新 目标和它的观察者依赖于通知机制来保持一致。但到底哪一个对象来调用notifyObserver()来触发更新?此时有两个选择:
- 有目标对象的状态设定操作在该表目标对象的状态后自动调用notifyObeserver() 例如下面目标类中的WeatherData.java中的**setMeasurements()**方法那样。这种方法的优点是客户不需要记住要在目标对象上调用notifyObeserver(),缺点是多个连续的操作会产生多次连续的更新,可能效率较低
- 让客户负责在适当的时候调用nitifyObserver() 这样做的优点是客户可以在一列状态改变完成之后在一次性地触发更新,避免了不必要的中间更新。缺点是给客户增加了触发更新的责任。由于客户可能会忘记调用notifyObserver(),这种方式较易出错。
-
4.对已删除目标的悬挂引用 删除一个目标是应注意不要在其观察者中遗留对该目标的悬挂引用,一种避免悬挂引用的方法是,当一个目标被删除时,让它通知它的观察者将对该目标的引用复位。一般来说,不能简单地删除观察者,因为其他的对象可能会引用它们,或者可能还在观察其他的目标。
-
5.在发出通知前确保目标的状态自身是一致的 在发出通知前确保状态自身一致这一点很重要,因为观察者在更新其状态的过程中需要查询目标的当前状态。 当Subject的子类调用继承的该项操作是,很容易无意中违反这条规则。例如下面的代码序列中,在目标尚处于一种不一致的状态时,通知就被触发了:
public class MySubject extends BaseClassSubject { private int temputure; @Override public void setMeasurements(int temputure) { super.setMeasurements(temputure); // 触发更新通知 this.temputure = temputure; // 子类更新太晚了 } }
可以使用模板方法(Template Method)模式发送通知来避免这种错误。定义一些子类可以重定义的hook方法,并将notifyObserver作为模板方法中的最后一个操作,这样当子类重定义了Subject的操作时,还可以保证该对象的状态时自身一致的。
如下:
public class A{ private int temputure; public final setMeasurements(int temputure){ translateToOtherFormate(tempure) notifyObserver(); } // 子类可以重写这个方法 来把温度设置为自己想要的格式 public void translateToOtherFormater(int temputure){ this.temputure = temputure; } public void notifyObserver() { //.......具体的通知操作 } }
顺便提一句,在文档中记录 是哪一个Subject操作 触发通知是应该的。
-
6.避免特定根据于观察者的更新协议——推/拉模型 观察者模式的实现经常需要让目标广播关于其改变的一些其它信息。目标将这些信息作为Update操作一个参数传递出去,这些信息的量可能很小,也可能很大。一个极端情况时,目标向观察者发送关于改变的详细信息,而不管他们需要与否。我们称之为推模型(push model)。另一个极端情况时拉模型(pull model);目标除最小通知外什么也不送出,而在此之后由观察者显示地向目标询问细节。 拉模型强调的是目标不知道它的观察者,而推模型假定目标知道一些观察者的需要的信息。推模型可能使得观察者相对难以复用,因为目标对观察者的假定可能不总是正确的,另一方面,拉模型可能效率较差,因为观察者对象需在没有目标对象帮助的情况下确定什么改变了。
-
7.显式地指定感兴趣的的改变 你可以扩展目标的注册接口,让各观察者注册为仅对特定事件感兴趣的,以提高更新的效率。当一个事件发生时,目标仅通知那些已注册为该事件感兴趣的的观察者。支持这种做法的一种途径是,对观察者使用目标对象的**方面(aspects)**的概念,可用如下代码将观察者对象注册为对目标对象的某些特定事件感兴趣:
public class