观察者模式是一种软件设计模式,其中一个名为主体 (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 的原创文章,尽在:” 汪子熙 ”: