观察者模式-将消息通知给观察者

公号:码农充电站pro
主页:https://codeshellme.github.io

观察者模式Observer Design Pattern)也被称为发布订阅模式Publish-Subscribe Design Pattern),主要用于更好的解决向对象通知消息的问题。

观察者模式定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。

观察者模式可以用很多称呼来描述,比如:

  • Subject-Observer:主题-观察者。
  • Publisher-Subscriber:发布者-订阅者。
  • Producer-Consumer:生产者-消费者。

1,订阅报纸

我们以订阅报纸为例,来描述 SubjectObserver 之间的关系。

Subject 相当于报社Observer 就相当于订阅报纸的用户

  • 从报社的角度来说:
    • 报社可向用户提供新闻消息,用户可以订阅报社的报纸,也可以取消订阅。
    • 报社记录了所有订阅报纸的用户名单
      • 如果有用户订阅了报纸,报社就会在名单中加入他的名字。
      • 如果有用户取消了报纸,报社就会从名单中删去他的名字。
    • 当报社有了新闻,会主动将新闻通知给它的所有订阅用户。
    • 没有订阅的用户,报社则不会去通知他。
  • 从用户的角度来说:
    • 如果用户订阅了报社的报纸,当报社有了新闻,他就会收到报社的消息。
    • 如果用户取消了报社的报纸,当报社有了新闻,报社也不会通知他。

SubjectObserver一对多关系

在这里插入图片描述

2,观察者模式类图

这里直接给出观察者模式的类图,这是最经典的实现方式,其它的变种都可以在它的基础上加以改进。

在这里插入图片描述

从类图中可以知道,Subject 是一个接口,有三个抽象方法:

  • registerObserver:用于注册 observer
  • removeObserver:用于移除 observer
  • notifyObservers:当有了消息,通知所有 observers

Observer 也是一个接口,有一个抽象方法:

  • update:当 subject 发来新消息时,用于更新消息。

3,实现观察者模式

下面我们来用代码实现观察者模式。

首先是两个接口 SubjectObserver

interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers(String info);
}

interface Observer {
    void update(String info);
}

这两个接口完全是按照类图中的内容来实现的,其中变量 info 的类型可以根据实际的应用场景来定。

再实现 ConcreteSubjectConcreteObserver

class ConcreteSubject implements Subject {

	// 用于存放 observer
    private final ArrayList<Observer> observers;

    public ConcreteSubject() {
        observers = new ArrayList();
    }

    public void registerObserver(Observer o) {
        observers.add(o);
    }

    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    public void notifyObservers(String info) {
        for (Observer o: observers) {
            o.update(info);
        }
    }
}

class ConcreteObserver implements Observer {
    private final String name;

    public ConcreteObserver(String name) {
        this.name = name;
    }

    public void update(String info) {
        System.out.println(this.name + " get info: " + info);
    }
}

ConcreteSubject 中的 observers 用于存储观察者,这里使用的类型是 ArrayList,也可以根据实际的应用场景来选择。

ConcreteObserver 中的 name 只是为了表示不同的观察者,观察者在收到消息后,将消息打印在控制台。

测试这两个类:

// 创建被观察者
ConcreteSubject s = new ConcreteSubject();

// 创建两个观察者
ConcreteObserver o1 = new ConcreteObserver("o1");
ConcreteObserver o2 = new ConcreteObserver("o2");

// 注册观察者
s.registerObserver(o1); // 注册 o1
s.registerObserver(o2); // 注册 o2
s.notifyObservers("info1");  // 向观察者通知消息

System.out.println("remove observer o1");

s.removeObserver(o1);  // 移除 o1
s.notifyObservers("info2"); // 再向观察者通知消息

输出如下:

o1 get info: info1
o2 get info: info1
remove observer o1
o2 get info: info2

可以看到,第一次通知消息时,o1o2 都收到消息了,在移除 o1 之后再发送消息,只有 o2 能收到消息。

这就是观察者模式最简洁的一种实现方式,非常简单。我把完整的代码放在了这里

4,观察者模式扩展

根据不同的应用场景和需求,观察者模式可以有不同的实现方式,比如下面几种:

  • 同步阻塞的实现方式
  • 异步非阻塞的实现方式
  • 进程内的实现方式
  • 跨进程的实现方式

同步阻塞方式

根据这种划分方式,上面我们实现的就是同步阻塞的方式,当有新消息的时候,Subject 会将消息 notify 给所有的 Observer,直到所有的 Observer 执行完毕它的 update 过程,整个通知过程才算完毕,这整个过程是一个阻塞的过程。

异步非阻塞方式

为了加快整个 notify 过程,我们可以将同步阻塞的方式改为异步非阻塞的方式。

一种简单的实现就是使用线程,就是在 update 方法中使用线程来完成任务,如下:

public void update(String info) {
   Thread t = new Thread(new Runnable() {
       public void run() {
           // 处理任务
       }
   });

   t.start();
}

Google Guava EventBusExplained 是一个通用的观察者模式框架,你可以去了解一下。

跨进程方式

同步阻塞异步非阻塞都属于进程之内的实现,对于跨进程的实现,一般都是基于消息队列来实现。至于这方面的应用,有很多现成的,成熟的组件可以使用,比如:

  • Redis Pub/Sub:一个快速、稳定的发布/订阅消息传递系统。
  • ActiveMQ:一个基于 Java 的多协议的消息传递服务。
  • RocketMQ:一个消息传递引擎。
  • Kafka:一个分布式的大数据流处理平台。

5,总结

观察者模式旨在将观察者与被观察者解耦,在很多地方都用到了该模式,比如 SwingJavaBeans 等。

观察者模式最经典的实现方式很简单,在实际应用中,可以在其基础上进行改进。

(本节完。)

推荐阅读:

设计模式之高质量代码

单例模式-让一个类只有一个对象

工厂模式-将对象的创建封装起来

策略模式-定义一个算法族

欢迎关注作者公众号,获取更多技术干货。

码农充电站pro

相关文章

什么是设计模式一套被反复使用、多数人知晓的、经过分类编目...
单一职责原则定义(Single Responsibility Principle,SRP)...
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强...
适配器模式将一个类的接口转换成客户期望的另一个接口,使得...
策略模式定义了一系列算法族,并封装在类中,它们之间可以互...
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,...