SuperAPI AI 工具集

observer-pattern

编程与开发

查看项目

更新于 10-15 14:22

技能介绍

用途

观察者模式用于事件驱动的通信。通过该模式,可以把某些对象(观察者,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