5.7-Observer「publish-subscribe,dependent」-观察者-发布订阅-依赖

用途

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

示例

将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性

视图 View 依赖数据 Data 一个目标 Subject/publish 可以有任意数目的依赖它的观察者 Observer/subscribe。一旦目标的状态发生改变,所有的观察者都得到通知。 收到通知后,每个观察者都将查询目标以使其状态与目标的状态同步。

适用性

  • 一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中,以使它们可以各自独立地改变和复用。
  • 对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
  • 一个对象必须通知其他对象,而它又不能假定其他对象是谁。换言之,你不希望这些对象是紧密耦合的。

结构

  • Subject(目标)
    • 目标知道它的观察者。可以有任意多个观察者观察同一个目标。
    • 状态改变时,通知自身的观察者
    • 提供注册和删除观察者对象的接口。
  • ConcreteSubject(具体目标):
    • 为观察者提供获取/设置状态的接口
  • Observer(观察者):为那些在目标发生改变时需要获得通知的对象定义一个更新接口。
  • ConcreteObserver:
    • 维护一个指向 ConcreteSubject 对象的引用
    • 存储状态,这些状态与 ConcreteSubject 的状态保持一致
    • 实现 Observer 的更新接口,以使自身状态与目标的状态保持一致。

交互时序

  • 当 ConcreteSubject 发生任何可能导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者。
  • 在得到一个具体目标的改变通知后,ConcreteObserver 对象可向目标对象查询信息。ConcreteObserver 使用这些信息使它的状态与目标对象的状态一致。

注意

  • 观察者 setState 后不会立即更新自身状态,而是等到被 notify 后再更新
  • Notify 不总是由目标对象调用,它也可被一个观察者或其他对象调用。

效果

Publisher 和 Subscriber 彼此独立,允许增加新的观察者而无需对现有的 Publisher/Subscriber 做任何修改

  • 低耦合:一个目标所知道的仅仅是它有一系列观察者,每个都符合抽象的 Observer 类的简单接口,而不知道其具体类型
    • 一个处于较低层次的目标对象可与一个处于较高层次的观察者通信并通知它,反之则不行:
      • 如果目标和观察者混在一块,那么得到的对象要么横贯两个层次(违反了层次性),要么必须放在这两层的某一层中(这可能会损害层次抽象)。
  • 支持广播通信
  • 意外的更新:
    • 在目标上一个看似无害的的操作可能会引起一系列对观察者以及依赖于这些观察者的对象的更新
    • 如果依赖准则的定义或维护不当,常常会引起错误的更新,这种错误通常很难捕捉。

实现

创建目标到其观察者之间的映射

  • 方法 1:显式地在目标中保存对它自身持有的观察者的引用
  • 方法 2:用一个关联查找机制(例如一个 hash 表)来维护目标到观察者的映射

观察多个目标:例如,一个表格对象可能依赖于多个数据源

  • 此时,必须扩展 Update 接口以使观察者知道是哪个目标送来的通知(目标对象可以简单地将自己作为 Update 操作的一个参数,让观察者知道应去检查哪个目标。)

谁触发更新:客户和发布者均可以调用发布者的 Notify 来触发更新:

  • 由目标对象的 setState 操作在自身状态被改变后自动调用 Notify
    • 优点是客户不需要记住要在目标对象上调用 Notify
    • 缺点是多个连续的操作会产生多次连续的更新
  • 让客户负责在适当的时候调用 Notify
    • 客户可以在一系列的状态改变完成后一次性地触发更新
    • 缺点是给客户增加了触发更新的责任。由于客户可能会忘记调用 Notify,这种方式较易出错

避免对已删除发布者的空引用:当一个目标被删除时,让它通知它的观察者将对该目标的引用复位

确保发出通知时,自身状态完成更新:Publisher 的子类重载操作时容易出现这种问题:

  • 你可以用抽象的 Subject 类中的模板方法(Template Method(5.10))发送通知来避免这种错误。定义那些子类可以重定义的原语操作,并将 Notify 作为模板方法中的最后一个操作,这样当子类重定义了 Subject 的操作时,还可以保证该对象的状态是自身一致的。
  • 日志应当记录是哪个 Publisher 触发了该操作

观察者模式的实现经常需要让目标广播关于其改变的其他一些信息:

  • Push 模式:目标向观察者发送关于改变的详细信息,而不管它们需要与否。
    • 强调的是目标不知道它的观察者
    • 推模型可能使得观察者相对难以复用,因为目标对观察者的假定可能并不总是正确的
  • Pull 模式:目标除最小通知外什么也不送出,而在此之后由观察者显式地向目标询问细节
    • 假定目标知道一些观察者需要的信息
    • 效率可能更差

订阅特定事件:可以扩展目标的注册接口,让各观察者注册为仅对特定事件感兴趣,以提高更新的效率

  • 支持这种做法的一种途径是,使用目标对象的方面 (aspect)的概念:
1
2
void Subject::Attach(Observer *,Aspect &interest);
void Observer::Update(Subject *,Aspect &interest);

封装复杂的更新语义: 当目标和观察者间的依赖关系特别复杂时,可能需要一个维护这些关系的对象。我们称这样的对象为更改管理器 ChangeManager。 ChangeManager 主要负责三方面工作: - 它将一个目标映射到它的观察者并提供一个接口来维护这个映射。这就不需要由目标来维护对其观察者的引用,反之亦然。 - 它定义一个特定的更新策略。 - 根据一个目标的请求,它更新所有依赖于这个目标的观察者。

例如,如果一个操作涉及对几个相互依赖的目标进行改动,就必须保证仅在所有的目标都已更改完毕后,才一次性地通知它们的观察者,而不是每个目标都通知观察者。

结合目标类和观察者类:不支持多重继承的语言通常不单独定义 Subject 和 Observer 类,而是将它们的接口结合到一个类中。这就允许你定义一个既是目标又是观察者的对象,而不需要多重继承