设计模式-观察者模式

行为型模式

解决的问题

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

这里讲的是"对象的状态改变",对象的状态就是封装在对象内部的字段值。

当对象的状态变化时,理论上应该是调用了对象的 set 方法,此时要去通知"依赖它的对象"去更新自身。需要关注两个问题:如何维护"依赖它的对象"?如何"通知"?
为什么会有上面这两个问题?

因为面向对象编程,是把现实问题建模成协作的对象。将一个系统分割成一系列相互协作的类,有一个常见的副作用:需要维护相关对象间的一致性。

但我们又不希望为了维护一致性而使各个类紧密耦合,这样会降低它们的可重用性。例如,B 对象依赖了 A 对象,如果在 A 的类中创建 B 的类,那么 B 的类只能被 A 使用,不能被复用。

Observer 模式描述了如何建立这种关系,这一模式的关键对象是"目标(subject)和观察者(observer)",一个目标可以有任意数目的依赖于它的观察者,一旦目标的状态发生改变,所有的观察者都得到通知。作为对这个通知的响应,每个观察者将查询目标以使自身的状态与目标的状态同步。

组成

参与者:

Subject(目标)

  • 目标知道它的观察者,可以有任意多个观察者
  • 提供注册和删除观察者的方法

Observer(观察者)

  • 在目标发生变化时需要获得通知的对象所定义的接口
  • 提供一个更新方法

ConcreteSubjet(具体目标)

  • 当它的状态发生变化时,向它的各个观察者发出通知

ConcreteObserver(具体观察者)

  • 维护一个指向 ConcrectSubject 对象的引用
  • 实现 Observer 接口的更新方法,存储有关状态,这些状态应与目标的状态保持一致

当 ConcreteSubjet 发生任何可能导致其状态变化时,它将通知(notify() 方法)它的各个观察者;在得到一个具体目标的改变通知后,ConcreteObserver 对象可向目标对象查询信息,使用这些信息以使它的状态与目标对象的状态保持一致。

应用场景

存在以下任一情况,可以使用观察者模式:

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

关键词:依赖、通知、改变、松耦合

关于"抽象、建模":

我们时常说要对业务做抽象和建模,实际上主要的就是,从中找出对象(方法、状态),以及对象之间的关联关系(依赖关系、状态变化关系)。

示例代码

JDK 自带 Subjet 和 Observer,不过自从 JDK9 后就被废弃了。JDK 自带的代码很简单,如下:

java 复制代码
// Observer 只提供一个方法
public interface Observer {
    void update(Observable o, Object arg);
}
java 复制代码
// Subject
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    public Observable() {
        obs = new Vector<>();
    }

    // 添加 Observer
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

   
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    
    public void notifyObservers() {
        notifyObservers(null);
    }

    
    public void notifyObservers(Object arg) {
        
        Object[] arrLocal;

        synchronized (this) {
            // 有两个竞态条件:
            // 1、一个新增的 Observer 没有被通知到
            // 2、一个被移出的 Observer 被通知了
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        // 因为不知道 Observer update 方法的耗时,所以要放在锁外面执行
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }


    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }

    public synchronized int countObservers() {
        return obs.size();
    }
}

优点和缺点

1、目标和观察者间的抽象耦合

一个目标所知道的仅仅是,它有一些列的观察者,目标不知道任何一个观察者所属的具体的类。这样允许我们在目标和观察者各自无感知的情况下,单独改变目标和观察者。

2、支持广播通信

目标发送通知不需要指定它的接收者,通知被自动广播给所有已经等级的观察者。

3、频繁的意外更新

因为目标不知道自己有多少个观察者,所以它在每一次改变时,都有可能引起一系列观察者频繁更新,会导致 notify 方法效率降低。

对于这种情况,如果不是"强一致"需求情况下,可以通过"中间组件、中间件"来维护依赖关系,从而实现异步更新。

4、对已删除目标的悬挂引用

删除一个目标时要注意,不能在其观察者中遗留对该目标的悬挂引用。

但一个目标被删除时,让它通知它的观察者将对该目标的引用复位。

总结

Observer 模式用于解决"一个对象状态变化,依赖于它的其他对象同时更新状态"的问题,并且让目标对象和观察者对象之间保持松耦合。

相关推荐
why1512 小时前
腾讯(QQ浏览器)后端开发
开发语言·后端·golang
浪裡遊2 小时前
跨域问题(Cross-Origin Problem)
linux·前端·vue.js·后端·https·sprint
声声codeGrandMaster2 小时前
django之优化分页功能(利用参数共存及封装来实现)
数据库·后端·python·django
呼Lu噜3 小时前
WPF-遵循MVVM框架创建图表的显示【保姆级】
前端·后端·wpf
bing_1583 小时前
为什么选择 Spring Boot? 它是如何简化单个微服务的创建、配置和部署的?
spring boot·后端·微服务
学c真好玩3 小时前
Django创建的应用目录详细解释以及如何操作数据库自动创建表
后端·python·django
Asthenia04123 小时前
GenericObjectPool——重用你的对象
后端
Piper蛋窝3 小时前
Go 1.18 相比 Go 1.17 有哪些值得注意的改动?
后端
excel3 小时前
招幕技术人员
前端·javascript·后端
盖世英雄酱581364 小时前
什么是MCP
后端·程序员