设计模式-观察者
观察者模式
动机
我们谈论面向对象编程不能不考虑对象的状态。毕竟面向对象编程是关于对象和它们之间的交互的。在某些对象需要获知其他对象频繁发生的变化的情况下。有一个良好的设计意味着可以尽可能解耦并减少依赖。当一个主体要被一个或多个观察者观察时可以使用观察者设计模式。
我们假设有一个股票系统,为几种类型的客户端提供数据。我们要一个以 web 为基础实现的应用的客户端,但是不久我们需要增加移动设备,掌上电脑的客户端,或者要一个系统通过 sms 短信通知用户。现在 :我们需要把主体(股票服务端)同它的观察者(客户端应用)分离,通过这种方式,增加新观察者对于服务端将会是透明的。
目的
定义一个对象间的一对多的依赖,以便当一个对象状态改变时,所有它的依赖都自动被通知并更新。
实现
参与这个模式的类有:
Observable 定义了在客户机附加和删除附加观察者操作的接口或抽象类,在 GOF 书中,这个类/接口被认为是主体。
ConcreteObservable 具体的 Observable 类,它维护对象的状态,当状态发生变化时,它通知附加的观察者。
Observer 定义了通知这个对象操作的接口或抽象类。
ConcreteObserverA, ConcreteObserverB 具体的 Observer 实现。
流程很简单:主框架实例化具体的 ConcreteObservable 对象,然后它使用 Observable 接口定义的方法实例化并附加具体的观察者。主体的状态每次的变化会使用 Observer 接口中的方法通知所有附加的 Observer。当应用增加了一个新的 Observer,我们仅需要做的是在主框架中将它实例化,并把它附加到 Observable 对象。已经创建的对象不需要更改。
适用范围和示例
经常需要实现观察者模式的情况有:
对象中的某个状态的变化必须反映在另一个对象中,对象间不紧耦合。
我们写的框架未来需要以微小的更改来扩展新观察者。
一些典型的示例:
模型-视图-控制器 观察者模式被用在 模型-视图-控制器(MVC)架构模式。在 MVC 中,这个模式用来解耦模型和视图。视图代表 Observer,模型是 Observable 对象。
事件管理 这是观察者模式广泛使用的的领域。Swing 和 .Net 都广泛使用观察者模式来实现事件机制。
示例 - 通讯社
举个通讯社的例子。一个通讯社收集新闻并把它们发布给不同的订阅者。我们需要创建一个框架,当事件发生时,通讯社能立即通知这个事件的订阅者。订阅者能以不同的方式接收这条新闻:电子邮件,SMS 短信,... 解决方案需要足够广泛来支持新类型的订阅者(可能会出现新的通讯科技)。
显然, 中介由一个 名为 NewsPublisher 的 Observable(主体) 类代表。它创建成一个抽象类,因为中介要创建几种 Observable 对象。最初只是商业新闻,但一段时间过后,将会发布体育和时政新闻。具体类是 BusinessNewsPublisher.
observer 逻辑是在 NewsPublisher 中实现,它保存了所有订阅者的清单并通知他们最新的新闻。订阅者由一些 observer (SMSSubscriber, EmailSubscriber) 代表。 上述两个 observer 继承自订阅者。订阅者是发布者知道的抽象类。发布者不知道具体的 observer,它只知道它们的抽象。
在 main 类中一个发布者(Observable) 内置一些订阅者(Observers)。这些订阅者向发布者订阅,也能被取消订阅。 在这个架构中能很容易的增加新的订阅者类型(实例 messaging, ...) 和新的发布者类型(天气新闻, 体育新闻, ...).
特殊问题和实现
多个主体对多个观察者
这不是常见的情况, 但有些情况是有许多观察者需要观察多于一个主体, 栽这种情况下,观察者需要不仅被通知变化, 还要被通知是哪一个主体的状态改变了。 实现非常简单,可以在更新通知方法中增加主体的引用。当通知观察者时主体将传入一个自身的引用(this)。
谁触发了更新?
主体和它的观察者之间通过在观察者接口中声明的通知方法完成通信。 但通信是由主体和观察者对象中的谁触发的呢。 通常这个通知方法是当主体的状态改变时通过主体触发的。 但当主体连续改变,更新频繁时将会决定观察者中的许多不必要的刷新操作。 为了使这个过程更高效,观察者可以被做成当它认为必要的时候负责起动通知操作。
确信主体的状态在通知前是前后一致的
当通知操作被触发时,主体状态应该是一致的。如果主体状态在观察者被通知后才改变,它将用一个旧的状态来更新。这似乎很难实现,但实际上当主体的子类操作调用继承的操作时是很容易实现的。在下面的示例中,当主体状态不一致的时候观察者被通知:
class Observable{ ... int state = 0; int additionalState = 0; public updateState(int increment) { state = state + increment; notifyObservers(); } ... } class ConcreteObservable extends Observable{ ... public updateState(int increment){ super.updateState(increment); // the observers are notified additionalState = additionalState + increment; // the state is changed after the notifiers are updated } ... }
这个陷阱可以使用抽象的主体父类中的模版方法来调用通知操作避免。然后主体子类将实现这个模版的操作:
class Observable{ ... int state = 0; int additionalState = 0; public void final updateState(int increment) { doUpdateState(increment); notifyObservers(); } public void doUpdateState(int increment) { state = state + increment; } ... } class ConcreteObservable extends Observable{ ... public doUpdateState(int increment){ super.doUpdateState(increment); // the observers are notified additionalState = additionalState + increment; // the state is changed after the notifiers are updated } ... }
主体基类中定义的触发通知操作的操作应该记录。
推送和拉取通讯方法
当主体状态变化时,从主体传数据给观察者有两种方法:
- 推送模式 - 主体发送变化的详细信息给观察者,不管它用还是不用。 因为主体需要发送详细信息给观察者,当数据量大并且观察者不用时,可能会降低效率。 另一个途径可能是只发送给需要这些信息的观察者。 这种情况下,主体应该能够区分不同类型的观察者并知道它们各自需要的数据,意味着主体层与观察者层更耦合。
- 拉取模式 - 主体仅仅通知观察者当它的状态发生改变时,每个观察者负责从主体拉取需要的数据。 因为通讯要两步完成,所以可能会降低效率,问题可能出现在多线程环境中。
热点
通过指明每个观察者感兴趣的事件,可以提高效率。可以通过增加一个新类,定义一个切面来实现。 当一个观察者注册了它,将会提供它感兴趣的切面:
class Subject{ ... void attach(Observer observer, Aspect interest); ... }
封装复杂语义
当我们有几个主体和观察者时,他们之间的关系会变得更复杂。首先时有多对多关系, 直接管理更困难, 其次主体和观察者间的关系可以包含一些逻辑。可能我们要一个观察者只有当所有主体的状态变化时才被通知。这种情况下我们应该引入另一个对象(称为 变化管理器) 负责下面的行为:
- 维护主体和观察者的多对多关系。
- 封装通知观察者的逻辑。
- 从主体接收通知并委托给观察者(基于封装的逻辑)
基本上变化管理器是一个观察者,因为它获得主体的变化通知。 同时它也是一个主体,因为它通知观察者。变化管理器是一个中介模式的实现。
观察者模式通常与其他设计模式组合使用:
- 工厂模式 - 非常有可能使用工厂方法来创建观察者,甚至主框架都不需要做改变,新观察者可以直在配置文件中接增加。
- 模板方法 - 观察者模式可以和模版方法模式结合使用以确保主体状态在通知前是首尾一致的。
- 中介模式 - 当有多个主体多多个观察者的复杂情况时,可以使用中介模式。
posted on 2016-03-01 23:37 Sky.Y.Chen 阅读(132) 评论(0) 收藏 举报