观察者模式-将消息通知给观察者
公号:码农充电站pro
主页:https://codeshellme.github.io
观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern),主要用于更好的解决向对象通知消息的问题。
观察者模式定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
观察者模式可以用很多称呼来描述,比如:
- Subject-Observer:主题-观察者。
- Publisher-Subscriber:发布者-订阅者。
- Producer-Consumer:生产者-消费者。
1,订阅报纸
我们以订阅报纸为例,来描述 Subject 与 Observer 之间的关系。
Subject 相当于报社,Observer 就相当于订阅报纸的用户:
- 从报社的角度来说:
- 报社可向用户提供新闻消息,用户可以订阅报社的报纸,也可以取消订阅。
- 报社记录了所有订阅报纸的用户名单。
- 如果有用户订阅了报纸,报社就会在名单中加入他的名字。
- 如果有用户取消了报纸,报社就会从名单中删去他的名字。
- 当报社有了新闻,会主动将新闻通知给它的所有订阅用户。
- 没有订阅的用户,报社则不会去通知他。
- 从用户的角度来说:
- 如果用户订阅了报社的报纸,当报社有了新闻,他就会收到报社的消息。
- 如果用户取消了报社的报纸,当报社有了新闻,报社也不会通知他。
Subject 与 Observer 是一对多关系:
2,观察者模式类图
这里直接给出观察者模式的类图,这是最经典的实现方式,其它的变种都可以在它的基础上加以改进。
从类图中可以知道,Subject 是一个接口,有三个抽象方法:
registerObserver
:用于注册 observer。removeObserver
:用于移除 observer。notifyObservers
:当有了消息,通知所有 observers。
Observer 也是一个接口,有一个抽象方法:
update
:当 subject 发来新消息时,用于更新消息。
3,实现观察者模式
下面我们来用代码实现观察者模式。
首先是两个接口 Subject 与 Observer:
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers(String info);
}
interface Observer {
void update(String info);
}
这两个接口完全是按照类图中的内容来实现的,其中变量 info
的类型可以根据实际的应用场景来定。
再实现 ConcreteSubject 和 ConcreteObserver :
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
可以看到,第一次通知消息时,o1 和 o2 都收到消息了,在移除 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,总结
观察者模式旨在将观察者与被观察者解耦,在很多地方都用到了该模式,比如 Swing,JavaBeans 等。
观察者模式最经典的实现方式很简单,在实际应用中,可以在其基础上进行改进。
(本节完。)
推荐阅读:
欢迎关注作者公众号,获取更多技术干货。