观察者模式是一种软件设计模式,其中一个名为主体(Subject)的对象维护其依赖项列表,称为观察者,并通常通过调用它们(observers)的方法之一来自动通知它们任何状态更改。
观察者模式主要用于在“事件驱动”软件中实现分布式事件处理系统。在这些系统中,主体 Subject 通常被称为“事件流(stream of events)”或“事件流源”,而观察者被称为“事件接收器”。
流命名法暗示了一种物理设置,其中观察者在物理上是分开的,并且无法控制从主题/流源发出的事件。
这种模式非常适合任何进程,其中数据从启动时 CPU 不可用的某些输入到达,而是“随机”到达(HTTP 请求、GPIO 数据、来自键盘/鼠标/的用户输入...、分布式数据库)和区块链,...)。
大多数现代编程语言都包含实现观察者模式组件的内置“事件”结构。虽然不是强制性的,但大多数“观察者”实现将使用后台线程监听主题事件和内核提供的其他支持机制(Linux epoll,...)。
观察者设计模式是二十三个著名的“四人帮”设计模式之一,描述了如何解决反复出现的设计挑战,以设计灵活且可重用的面向对象软件,即更容易实现、更改、 测试和重用。
What problems can the Observer design pattern solve?
观察者模式解决了以下问题:
- 对象之间的一对多依赖关系应该在不使对象紧密耦合的情况下定义。
- 应该确保当一个对象改变状态时,自动更新无限数量的依赖对象。
- 一个对象可以通知无限数量的其他对象应该是可能的。
通过定义一个直接更新依赖对象状态的对象(主体)来定义对象之间的一对多依赖是不灵活的,因为它将主体耦合到特定的依赖对象。尽管如此,从性能的角度来看,或者如果对象实现是紧密耦合的(想想每秒执行数千次的低级内核结构),它仍然有意义。在某些情况下,紧密耦合的对象可能难以实现,并且难以重用,因为它们引用并了解(以及如何更新)具有不同接口的许多不同对象。在其他情况下,紧密耦合的对象可能是更好的选择,因为编译器将能够在编译时检测错误并在 CPU 指令级别优化代码。
What solution does the Observer design pattern describe?
定义主题和观察者对象。
这样当一个主题改变状态时,所有注册的观察者都会被自动通知和更新(可能是异步的)。
主体的唯一职责是维护观察者列表并通过调用它们的 update() 操作通知它们状态变化。 观察者的职责是在一个主题上注册(和取消注册)自己(以获得状态变化的通知)并在收到通知时更新他们的状态(将他们的状态与主题的状态同步)。 这使得主体和观察者松散耦合。 主体和观察者彼此之间没有明确的感知。 可以在运行时独立添加和删除观察者。 这种通知-注册交互也称为发布-订阅。
Strong vs. weak reference
观察者模式会导致内存泄漏,称为失效侦听器问题,因为在基本实现中,它需要显式注册和显式取消注册,就像在处置模式中一样,因为主体持有对观察者的强引用,使它们保持活动状态。 这可以通过主体持有对观察者的弱引用来防止。
Coupling and typical pub-sub implementations
通常,观察者模式被实现,因此被“观察”的“主体”是正在观察状态变化的对象的一部分(并传达给观察者)。这种类型的实现被认为是“紧密耦合的”,迫使观察者和主体相互了解并可以访问它们的内部部分,从而产生可扩展性、速度、消息恢复和维护(也称为事件或通知)的可能问题损失),条件分散缺乏灵活性,以及可能妨碍所需的安全措施。在发布-订阅模式(又名发布-订阅模式)的一些(非轮询)实现中,这是通过创建一个专用的“消息队列”服务器(有时还有一个额外的“消息处理程序”对象)作为额外阶段来解决的观察者和被观察对象之间,从而解耦组件。在这些情况下,消息队列服务器由观察者使用观察者模式访问,“订阅某些消息”只知道预期的消息(或在某些情况下不知道),而对消息发送者本身一无所知;发送者也可能对观察者一无所知。发布订阅模式的其他实现,实现了类似的通知和向感兴趣的各方通信的效果,根本不使用观察者模式。
在 OS/2 和 Windows 等多窗口操作系统的早期实现中,术语“发布-订阅模式”和“事件驱动的软件开发”被用作观察者模式的同义词。
正如 GoF 书中所描述的,观察者模式是一个非常基本的概念,并没有解决在通知观察者之前或之后消除对观察到的“主体”或被观察“主体”所做的特殊逻辑的更改的兴趣。该模式也不处理发送更改通知时的记录或保证收到更改通知。这些问题通常在消息队列系统中处理,其中观察者模式只是其中的一小部分。
观察者模式的 UML 和 时序图
在上面的UML类图中,Subject类并没有直接更新依赖对象的状态。 相反,Subject 引用 Observer 接口(update())来更新状态,这使得 Subject 独立于依赖对象的状态如何更新。 Observer1 和 Observer2 类通过将它们的状态与主题的状态同步来实现 Observer 接口。
UML 序列图显示了运行时交互:Observer1 和Observer2 对象调用Subject1 上的attach(this) 来注册它们自己。 假设 Subject1 的状态发生了变化,Subject1 会对其自身调用 notify() 。
notify() 对已注册的 Observer1 和 Observer2 对象调用 update(),它们从 Subject1 请求更改的数据 (getState()) 以更新(同步)它们的状态。
UML 类图
看一个 Java 的例子。
Subject 即数据源的实现:
import java.util.List;
import java.util.ArrayList;
import java.util.Scanner;
class EventSource {
public interface Observer {
void update(String event);
}
private final List<Observer> observers = new ArrayList<>();
private void notifyObservers(String event) {
observers.forEach(observer -> observer.update(event));
}
public void addObserver(Observer observer) {
observers.add(observer);
}
public void scanSystemIn() {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
notifyObservers(line);
}
}
}
Observer 的实现:
public class ObserverDemo {
public static void main(String[] args) {
System.out.println("Enter Text: ");
EventSource eventSource = new EventSource();
eventSource.addObserver(event -> {
System.out.println("Received response: " + event);
});
eventSource.scanSystemIn();
}
}
JavaScript 的实现:
let Subject = {
_state: 0,
_observers: [],
add: function(observer) {
this._observers.push(observer);
},
getState: function() {
return this._state;
},
setState: function(value) {
this._state = value;
for (let i = 0; i < this._observers.length; i++)
{
this._observers[i].signal(this);
}
}
};
let Observer = {
signal: function(subject) {
let currentValue = subject.getState();
console.log(currentValue);
}
}
Subject.add(Observer);
Subject.setState(10);
//Output in console.log - 10
更多Jerry的原创文章,尽在:"汪子熙":
网友评论