第22章 观察者模式
22.1 观察者模式概述
为了更好地描述对象之间存在的这种一对多(包括一对一)的联动,观察者模式应运而生,它定义了对象之间的一种一对多的依赖关系,让一个对象的改变能够影响其他对象。
观察者模式是使用频率较高的设计模式之一,它用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
观察者模式(Observer Pattern):定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时其相关依赖对象皆得到通知并被自动更新。
- 发布订阅模式(Publish-Subscribe)
- 模型视图模式(Model-View)
- 源-监听器模式(Source-Listener)
- 从属者模式(Dependents)
对象行为型模式
22.2 观察者模式结构与实现
22.2.1 观察者模式结构
(1)Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观
察者集合﹐一个观察曰e间了通知方法notify()。目标类可以是接口,也可以E家除观察者对象,同时它定义了通知方法 notify()。目标类可以是接口,也
体类。
(2) ConcreteSubject(具体目标):具体目标是目标类的子类,它通常
变的数据,当它的状态发生改变时将向它的各个观察者发出通知﹔同时
类中定义的抽象业务逻辑方法(如果有)。如果无须扩展目标类,则具体目标类可以省略:接
(3)Observer(观察者):观察者将对观察目标的改变作出反应,观察者一般定义为接
口,该接口声明了更新数据的方法 update(),因此又称为抽象观察者。
(4)ConcreteObserver(具体观察者):在具体观察者中维护一个指向用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致它实现了在
抽象观察者Observer 中定义的 update()方法。通常在实现时可以调用具体的标类的attach()方法将自己添加到目标类的集合中或通过detach()方法将自己从目标类的集合中
删除。
22.2.2 观察者模式实现
观察者模式应用实例
实例说明
在某多人联机对战游戏中,多个玩家可以加入同一战队组成联盟,当战队中的某一成员受到敌人攻击时将给所有其他盟友发送通知,盟友收到通知后将作出响应。
试使用观察者模式设计并实现该过程,以实现战队成员之间的联动。
实例分析及类图
联盟成员受到攻击→发送通知给盟友→盟友作出响应
- 抽象目标类
- AllyControlCenter
- 具体目标类
- ConcreteAllyControlCenter
- 抽象观察者
- Observer
- 具体观察者
- Player
实例代码
AllyControlCenter:指挥部(战队控制中心)类,充当抽象目标类。
ConcreteAllyControlCenter:具体指挥部类,充当具体目标类。
Observer:抽象观察者类。
Player:战队成员类,充当具体观察者类。
Client:客户端测试类。
结果及分析
Player.beAttacked()→AllyControlCenter.notifyObserver()→Player.help()
22.4 JDK 对观察者模式的支持
Observer 接口
抽象观察者类:
目标发生变化调用
void update(Observable o, Object arg);
在具体观察者类 ConcreteObserver ,有不同的 update() 方法实现。
当调用抽象观察目标类 Observable 的 notifyObservers() 执行。
Observable
抽象观察目标类:
定义一个向量存储观察者对象。
用户可以直接使用Observer接口和Observable类作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类。通过使用JDK中的Observer接口和Observable类可以更加方便地在Java语言中应用观察者模式。
22.5 观察者模式与 Java 事件处理
JDK 1.0及更早版本的事件模型基于职责链模式,但是这种模型不适用于复杂的系统,因此在JDK 1.1及以后的各个版本中事件处理模型采用基于观察者模式的委派事件模型(Delegation Event Model,DEM)——一个Java组件所引发的事件并不由引发事件的对象自己来负责处理,而是委派给独立的事件处理对象负责。
在DEM模型中目标角色负责发布事件,而观察者角色可以向目标订阅它所感兴趣的事件。当一个具体目标产生一个事件时它将通知所有订阅者。
- 事件的发布者称为事件源对象(Event Source object)
- 订阅者称为事件监听器(Event Listener)(可以在事件监听者的实现类中实现事件处理,又可以称为事件处理对象)
- 通过事件对象(Event Object) 来传递与事件相关的信息
事件源对象、事件监听对象(事件处理对象)和事件对象构成了Java事件处理模型的三要素。
事件源对象充当观察目标,而事件监听对象充当观察者。
举例:按钮单击事件
- 如果用户在GUI中单击一个按钮,将触发一个事件(如ActionEvent类型的动作事件),JVM将产生一个相应的ActionEvent类型的事件对象,在该事件对象中包含了有关事件和事件源的信息,此时按钮是事件源对象。
- 将ActionEvent事件对象传递给事件监听对象(事件处理对象),JDK提供了专门用于处理ActionEvent事件的接口ActionListener,开发人员需要提供一个ActionListener的实现类(如MyActionHandler),实现在ActionListener接口中声明的抽象事件处理方法actionPerformed(),对所发生事件做出相应的处理。
- 开发人员将ActionListener接口的实现类(如MyActionHandler)对象注册到按钮中,可以通过按钮类的addActionListener()方法来实现注册。
- JVM在触发事件时将调用按钮的fireXXX()方法,在该方法内部将调用注册到按钮中的事件处理对象的actionPerformed()方法,实现对事件的处理。
使用类似的方法可以自定义GUI组件,例如包含两个文本框和两个按钮的登录组件LoginBean,可以采用如图所示的设计方案。
LoginEvent
是事件类,用于封装与事件有关的信息,它不是观察者模式的一部分,但是可以在目标对象和观察者对象之间传递数据。在AWT事件模型中所有的自定义事件类都是 java.util.EventObject 的子类。LoginEventListener
充当抽象观察者,它声明了事件响应方法 validateLogin(),用于处理事件,该方法也称为事件处理方法。validateLogin()方法将一个LoginEvent类型的事件对象作为参数,用于传输与事件相关的数据,在其子类中实现该方法,实现具体的事件处理。LoginBean
充当具体目标类,在这里没有定义抽象目标类,对观察者模式进行了一定的简化。在LoginBean中定义了抽象观察者LoginEventListener类型的对象lel和事件对象LoginEvent,提供了注册方法addLoginEventListener()用于添加观察者,在Java事件处理中通常使用一对一的观察者模式,而不是一对多的观察者模式,也就是说一个观察目标中只定义一个观察者对象,而不是提供一个观察者对象的集合。在LoginBean中还定义了通知方法fireLoginEvent(),该方法在Java事件处理模型中称为“点火方法”,在该方法内部实例化了一个事件对象LoginEvent,将用户输入的信息传给观察者对象,并且调用了观察者对象的响应方法 validateLogin()。LoginValidatorA
和LoginValidatorB
充当具体观察者类,它们实现了在LoginEventListener接口中声明的抽象方法 validateLogin(),用于具体实现事件处理。该方法包含一个LoginEvent类型的参数,在LoginValidatorA和 LoginValidatorB类中可以针对相同的事件提供不同的实现。