用途
观察者模式用于事件驱动的通信。通过该模式,可以把某些对象(观察者,observers)订阅到另一个对象(可观察对象,observable)上。每当事件发生时,可观察对象会通知它的所有观察者。
何时使用
- 当你需要就状态变化或事件通知应用的多个部分时。
- 当需要在组件之间实现事件驱动、异步通信时。
何时不使用
- 当订阅者数量非常多、通知性能变得关键时。
- 当一对一通信使用更简单的回调或直接函数调用就足够时。
- 当隐式事件链带来的调试困难超过解耦带来的收益时。
主要能力与实现细节
一个可观察对象通常包含三个重要部分:
- observers:一个观察者数组,每当特定事件发生时它们会被通知。
- subscribe():用于将观察者添加到观察者列表的方法。
- unsubscribe():用于从观察者列表中移除观察者的方法。
- notify():每当特定事件发生时通知所有观察者的方法。
资料给出的基于 ES6 class 的实现:
class Observable {
constructor() {
this.observers = [];
}
subscribe(func) {
this.observers.push(func);
}
unsubscribe(func) {
this.observers = this.observers.filter((observer) => observer !== func);
}
notify(data) {
this.observers.forEach((observer) => observer(data));
}
}
通过 subscribe 方法可以把观察者加入观察者列表,通过 unsubscribe 方法移除观察者,通过 notify 方法通知所有订阅者。
使用方式(示例流程)
资料以一个基础应用为例,该应用只包含两个组件:一个 Button 和一个 Switch。
export default function App() {
return (
<div className="App">
<Button>Click me!</Button>
<FormControlLabel control={<Switch />} />
</div>
);
}
目标是跟踪用户与应用的交互:每当用户点击按钮或切换开关时,记录带时间戳的事件,并弹出一个 toast 通知。
首先创建 logger 和 toastify 函数,它们最终会从 notify 方法接收数据:
import { ToastContainer, toast } from "react-toastify";
function logger(data) {
console.log(`${Date.now()} ${data}`);
}
function toastify(data) {
toast(data);
}
export default function App() {
return (
<div className="App">
<Button>Click me!</Button>
<FormControlLabel control={<Switch />} />
<ToastContainer />
</div>
);
}
此时 logger 和 toastify 函数还不知道 observable,observable 无法通知它们。要让它们成为观察者,需要用 observable 的 subscribe 方法订阅它们:
import { ToastContainer, toast } from "react-toastify";
function logger(data) {
console.log(`${Date.now()} ${data}`);
}
function toastify(data) {
toast(data);
}
observable.subscribe(logger);
observable.subscribe(toastify);
export default function App() {
return (
<div className="App">
<Button>Click me!</Button>
<FormControlLabel control={<Switch />} />
<ToastContainer />
</div>
);
}
每当事件发生,logger 和 toastify 函数都会收到通知。接下来实现真正触发通知的函数:handleClick 和 handleToggle。这些函数应调用 observable 的 notify 方法,并传入观察者应接收的数据:
import { ToastContainer, toast } from "react-toastify";
function logger(data) {
console.log(`${Date.now()} ${data}`);
}
function toastify(data) {
toast(data);
}
observable.subscribe(logger);
observable.subscribe(toastify);
export default function App() {
function handleClick() {
observable.notify("User clicked button!");
}
function handleToggle() {
observable.notify("User toggled switch!");
}
return (
<div className="App">
<Button>Click me!</Button>
<FormControlLabel control={<Switch />} />
<ToastContainer />
</div>
);
}
完整流程为:handleClick 和 handleToggle 调用 observer 的 notify 方法并传入数据,随后 observer 通知订阅者(此例中为 logger 和 toastify 函数)。每当用户与任一组件交互,logger 和 toastify 函数都会收到通过 notify 方法传入的数据。
适用场景
观察者模式有多种用法,在处理异步、基于事件的数据时尤其有用。例如:希望某些组件在特定数据下载完成时收到通知,或当用户在留言板发送新消息时通知所有其他成员。
案例
使用观察者模式的流行库是 RxJS。RxJS 有大量内置特性和示例与观察者模式配合使用。
优点
使用观察者模式是强制实现关注点分离和单一职责原则的好方法。观察者对象与可观察对象并非紧密耦合,可以随时(解)耦。可观察对象负责监控事件,观察者只负责处理接收到的数据。
缺点
如果某个观察者变得过于复杂,在通知所有订阅者时可能引发性能问题。
来源
patterns.dev/vanilla/observer-pattern
参考
- RxJS
- JavaScript Design Patterns: The Observer Pattern