headfirst设计模式学习体会(2)

  许多GUI框架包括Swing大量使用观察者模式,比如按钮上的动作可能有很多“倾听者”。此模式也被应用在许多地方,例如:JavaBeans、RMI。
  观察者模式和报纸的订阅很相似,只不过出版者在此模式中改称为“主题(Subject)”,订阅者改称为“观察者(Observer)”。主题对象管理某些数据,或者说这些这些数据代表主题对象的状态。当主题对象的状态改变时,主题对象就会通知观察者对象。在程序中,“通知”意味着信息从一个对象流向另一个对象,而一个对象中新信息的引入也即意味着此对象状态的改变(否则信息的引入就没有意义,当然你也可以通过设置让对象什么都不做,就像信息没有流入这个对象一样)。于是观察者模式实际上建立的是这样一种一对多的依赖关系:程序中的一些对象的状态的改变依赖于某一个对象的状态的改变。当此对象状态改变的时候,就会“通知”依赖它的多个对象,触发这多个对象自动完成相应的状态更新。
  显然,观察者模式将程序里面状态迁移的逻辑自动交给对象之间的通信去完成,将这部分逻辑封装起来,客户只需要触发原始的状态改变(比如按下某个按钮),就可以让程序整体进行自发的状态迁移。那么在实现观察者模式的时候,我们需要遵循哪些原则呢?
  首先,程序里面的对象有各种不同的类型和功能,为了管理对象之间的通信,我们必须设置统一的接口。一个对象究竟应该怎么“通知”另一个对象,由这个统一的对象管理。这样不管对象本身的类型和功能是什么,或者程序是不是要引入新的对象,想要实现对象的通信只需按需实现自己作为主题对象或者观察者对象的接口即可。而从语义上来说,对象之间的通信通过接口来实现也是极其自然的。
  另一方面,使用接口,而不是继承。是因为对象自己本身在程序中有其他的角色和功能,它们可以是各种不同的类型。但只要它们实现了这个统一的接口,就可以进行对象之间的通信,而不影响对象本身的复用性。
  在观察者模式中,这种对象间通信的接口应该怎么设计呢?
  接口只是接口,我们是为了完成对象间的通信功能而去调用这个接口。具体的“通知”自然由具体的对象来怎么实现这个接口来决定。不同的对象,实现统一的接口,具体实现了怎样的通信功能留给对象自己按需处理(怎么处理主题对象状态改变的信息,怎么根据信息改变自己的状态)。那么我们只需要集中精力设计好这个统一的接口即可。
  设计原则:为了交互对象之间的松耦合设计而努力
  松耦合的设计原则意味着对象之间的接口要清晰明确,只完成相应的功能,不依赖对象内部具体的细节。这样的设计使得对象在实现接口的时候不影响自己的其他功能,不过度暴露自己的内部,保持自己接口功能之外的其他功能的复用性。那么我们看看观察者模式中需要怎么样的接口。
  首先,主题对象需要知道向哪些观察者发出通知。因此,它有必要在接口中记录需要被通知的观察者对象,它还要在有新对象请求加入的时候,提供将新对象加入记录的功能(注册),同样的,当有对象不再“订阅”主题对象的时候,它也要提供相应的删除该对象记录的功能。当然,接口最重要的是发出通知的功能。于是,主题对象要实现的接口包含以下三个方法:
  

1
2
3
  registerObserver();
  removeObserver();
  notifyObserver();

  另一方面,在观察者对象中,被通知就意味着自身状态的相应改变。这个改变就是对被通知的完成。所以观察者对象提供的接口需要包含改变自己状态的方法:
  

1
  update();

  现在我们看接口之间是怎么通信的,主题对象接口的notifyObserver()方法调用观察者对象的接口中的update()方法。主题对象怎么确定具体的观察者对象的接口的呢?其实,要明白调用的机制不是接口调用接口,而是对象通过接口调用另一个对象的接口。在实现了Subject接口的主题对象中保存了对观察者对象的引用变量列表,同样在观察者对象中保存了对主题对象的引用变量。这就如同第一章中鸭子类需要保存对实现了行为接口的行为类的引用变量一样。我们在需要引用接口的类中保存对接口的实现类的引用变量,这样就能确定究竟对接的是哪一个实现类的接口。
  观察者模式需要注意的一个地方是,程序如果依赖于观察者被通知的次序,可能会导致错误的结果。因为观察者被通知的次序与具体的实现有关。
  观察者模式 在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会接到通知,并自动更新。