3

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

 2 years ago
source link: https://codeshellme.github.io/2020/12/dp-observer/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

公号:码农充电站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:一个分布式的大数据流处理平台。

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

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

(本节完。)


推荐阅读:

设计模式之高质量代码

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

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

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


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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK