“观察者模式”原来还有个兄弟

167 阅读8分钟

观察者模式 Observer Pattern

观察者模式使用场景

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。

什么是观察者模式

实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。

定义:

指对象间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。

类结构图:

观察者模式的结构图

观察者模式的主要角色如下:

  1. 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  2. 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  3. 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  4. 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

观察者模式的优缺点

优点:
  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
  2. 目标与观察者之间建立了一套触发机制。
缺点:
  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

观察者模式使用时容易引发的问题

  1. 避免循环引用。
  2. 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
  3. 观察者模式会导致内存泄漏,称为失效侦听器问题,因为在基本实现中,它需要显式注册显式取消注册,就像在处置模式中一样,因为主体持有对观察者的强引用,使它们保持活动状态。 这可以通过主体持有对观察者的弱引用来防止。
// 强引用
abstract class Subject {
    protected List<Observer> observers = new ArrayList<Observer>();
    //增加观察者方法
    public void add(Observer observer) {
        observers.add(observer);
    }
    //删除观察者方法
    public void remove(Observer observer) {
        observers.remove(observer);
    }
    public abstract void notifyObserver(); //通知观察者方法
}
// 弱引用
abstract class Subject {
    protected List<WeakReference<Observer>> observers = new ArrayList<>();

    //增加观察者方法
    public void add(Observer observer) {
        observers.add(new WeakReference<>(observer));
    }

    //删除观察者方法
    public void remove(Observer observer) {
        for (WeakReference<Observer> observerReference : observers) {
            Observer ob = observerReference.get();
            if (ob == observer) {
                observers.remove(observerReference);
            }
        }
    }

    public abstract void notifyObserver(); //通知观察者方法
}

观察者模式 VS 发布订阅模式

观察者模式(Observer Pattern)

观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。

简单举例,报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是上面文章开头所说的“一对多”的依赖关系。

发布订阅模式(Pub-Sub Pattern)

其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。

但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。

在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。

简单举例,你在微博上关注了A,同时其他很多人也关注了A,那么当A发布动态的时候,微博就会为你们推送这条动态。A就是发布者,你是订阅者,微博就是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的。

观察者模式和发布订阅模式有什么区别?

先来看下这两个模式的实现结构:

观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心

在观察者模式中,观察者是有意识到抽象主题(Subject)的,并且在抽象主题(Subject)中维护着观察者的列表(List)。然而在发布订阅模式中却不相同。

在发布订阅模式中,组件是松散耦合的。发布者和订阅者不需要相互知道,他们仅仅是借助消息队列进行通信。

观察者模式几乎是以同步的方式实现。当事件发生的时候,抽象主题(Subject)调用它所有观察者的方法。发布订阅模式通过消息队列以异步的方式实现。

观察者模式需要在单应用地址空间中实现。另一方面,发布订阅模式可以是跨应用模式。

如何分辨设计模式

辨别模式的关键是设计意图还是设计结构(理念),在《JavaScript设计模式与开发实践》一书中有说分辨模式的关键是意图而不是结构

如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的;如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级。

设计模式的背景

设计模式并非是软件开发的专业术语,实际上,“模式”最早诞生于建筑学。

设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。通俗一点说,设计模式是在某种场合下对某个问题的一种解决方案。

这些“好的设计”并不是谁发明的,而是早已存在于软件开发中。一个稍有经验的程序员也许在不知不觉中数次使用过这些设计模式。GoF(Gang of Four–四人组,《设计模式》几位作者)最大的功绩是把这些“好的设计”从浩瀚的面向对象世界中挑选出来,并且给予它们一个好听又好记的名字。

设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案,他不是一个死的机制,他是一种思想,一种写代码的形式。每种语言对于各种设计模式都有他们自己的实现方式,

模式的社区一直在发展。GoF在1995年提出了23种设计模式,但模式不仅仅局限于这23种,后面增加到了24种。在这20多年的时间里,也许有更多的模式已经被人发现并总结了出来,比如一些JavaScript 图书中会提到模块模式、沙箱模式等。

参考

观察者模式(Observer模式)详解

设计模式中的观察者模式

观察者模式 vs 发布订阅模式

发布订阅模式与观察者模式