observer-pattern

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Observer Pattern

Observer Pattern(观察者模式)

With the observer pattern, we can subscribe certain objects, the observers, to another object, called the observable. Whenever an event occurs, the observable notifies all its observers!
借助Observer Pattern,我们可以将某些对象(即observers(观察者)订阅到另一个名为**observable(可观察对象)**的对象上。每当事件发生时,observable会通知所有它的observers!

When to Use

适用场景

  • Use this when you need to notify multiple parts of an application about state changes or events
  • This is helpful for implementing event-driven, asynchronous communication between components
  • 当你需要向应用的多个部分通知状态变更或事件时使用该模式
  • 这有助于在组件之间实现事件驱动的异步通信

Instructions

实现说明

  • Create an Observable class with
    subscribe
    ,
    unsubscribe
    , and
    notify
    methods
  • Keep observers loosely coupled to the observable for better separation of concerns
  • Be mindful of performance when notifying many subscribers with complex logic
  • Consider using libraries like RxJS for more advanced reactive programming needs
  • 创建一个包含
    subscribe
    unsubscribe
    notify
    方法的Observable类
  • 保持observers与observable的松耦合,以实现更好的关注点分离
  • 当通知大量带有复杂逻辑的订阅者时,注意性能问题
  • 若需要更高级的响应式编程功能,可以考虑使用RxJS这类库

Details

详细说明

An observable object usually contains 3 important parts:
  • observers
    : an array of observers that will get notified whenever a specific event occurs
  • subscribe()
    : a method in order to add observers to the observers list
  • unsubscribe()
    : a method in order to remove observers from the observers list
  • notify()
    : a method to notify all observers whenever a specific event occurs
Let's create an observable using an ES6 class:
js
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));
  }
}
We can now add observers to the list of observers with the subscribe method, remove the observers with the unsubscribe method, and notify all subscribers with the notify method.
Let's build something with this observable. We have a very basic app that only consists of two components: a
Button
, and a
Switch
.
js
export default function App() {
  return (
    <div className="App">
      <Button>Click me!</Button>
      <FormControlLabel control={<Switch />} />
    </div>
  );
}
We want to keep track of the user interaction with the application. Whenever a user either clicks the button or toggles the switch, we want to log this event with the timestamp. Besides logging it, we also want to create a toast notification that shows up whenever an event occurs!
Whenever the user invokes the
handleClick
or
handleToggle
function, the functions invoke the
notify
method on the observer. The
notify
method notifies all subscribers with the data that was passed by the
handleClick
or
handleToggle
function!
First, let's create the
logger
and
toastify
functions. These functions will eventually receive
data
from the
notify
method.
js
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>
  );
}
Currently, the
logger
and
toastify
functions are unaware of observable: the observable can't notify them yet! In order to make them observers, we'd have to subscribe them, using the
subscribe
method on the observable!
js
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>
  );
}
Whenever an event occurs, the
logger
and
toastify
functions will get notified. Now we just need to implement the functions that actually notify the observable: the
handleClick
and
handleToggle
functions! These functions should invoke the
notify
method on the observable, and pass the data that the observers should receive.
js
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>
  );
}
We just finished the entire flow:
handleClick
and
handleToggle
invoke the
notify
method on the observer with the data, after which the observer notifies the subscribers: the
logger
and
toastify
functions in this case.
Whenever a user interacts with either of the components, both the
logger
and the
toastify
functions will get notified with the data that we passed to the
notify
method!
Although we can use the observer pattern in many ways, it can be very useful when working with asynchronous, event-based data. Maybe you want certain components to get notified whenever certain data has finished downloading, or whenever users sent new messages to a message board and all other members should get notified.
一个observable对象通常包含三个重要部分:
  • observers
    :一个数组,存储特定事件发生时需要被通知的所有observers
  • subscribe()
    :用于将observers添加到observers列表的方法
  • unsubscribe()
    :用于将observers从observers列表中移除的方法
  • notify()
    :当特定事件发生时,通知所有observers的方法
让我们用ES6类创建一个observable:
js
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方法将observers添加到列表中,用unsubscribe方法移除observers,并用notify方法通知所有订阅者。
让我们用这个observable构建一个示例。我们有一个非常基础的应用,只包含两个组件:
Button
Switch
js
export default function App() {
  return (
    <div className="App">
      <Button>Click me!</Button>
      <FormControlLabel control={<Switch />} />
    </div>
  );
}
我们想要跟踪用户与应用的交互行为。每当用户点击按钮或切换开关时,我们都要记录该事件及时间戳。除了记录日志,我们还希望在事件发生时弹出一个提示通知!
每当用户触发
handleClick
handleToggle
函数时,这些函数会调用observable的
notify
方法。
notify
方法会将
handleClick
handleToggle
传递的数据通知给所有订阅者!
首先,让我们创建
logger
toastify
函数。这些函数最终会从
notify
方法接收
data
js
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还无法通知它们!为了让它们成为observers,我们需要使用observable的
subscribe
方法来订阅它们!
js
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
函数都会收到通知。现在我们只需要实现真正通知observable的函数:
handleClick
handleToggle
!这些函数应该调用observable的
notify
方法,并传递observers需要接收的数据。
js
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
调用observable的
notify
方法并传入数据,随后observable通知订阅者——在这个例子中就是
logger
toastify
函数。
每当用户与任意一个组件交互时,
logger
toastify
函数都会收到我们传递给
notify
方法的数据!
尽管Observer Pattern的使用场景很多,但它在处理异步、基于事件的数据时非常有用。比如,你可能希望某些组件在特定数据下载完成时收到通知,或者当用户向留言板发送新消息时,所有其他成员都能收到通知。

Case study

案例研究

A popular library that uses the observable pattern is RxJS.
ReactiveX combines the Observer pattern with the Iterator pattern and functional programming with collections to fill the need for an ideal way of managing sequences of events. - RxJS
RxJS has tons of built-in features and examples that work with the observable pattern.
一个使用Observer Pattern的热门库是RxJS。
ReactiveX结合了Observer模式、Iterator模式和集合的函数式编程,以满足管理事件序列的理想方式。——RxJS
RxJS拥有大量基于Observer Pattern的内置功能和示例。

Pros

优点

Using the observer pattern is a great way to enforce separation of concerns and the single-responsibility principle. The observer objects aren't tightly coupled to the observable object, and can be (de)coupled at any time. The observable object is responsible for monitoring the events, while the observers simply handle the received data.
使用Observer Pattern是实现关注点分离和单一职责原则的好方法。观察者对象与可观察对象之间不存在紧耦合,并且可以随时(解除)耦合。可观察对象负责监控事件,而观察者只需处理接收到的数据。

Cons

缺点

If an observer becomes too complex, it may cause performance issues when notifying all subscribers.
如果一个观察者变得过于复杂,在通知所有订阅者时可能会导致性能问题。

Source

来源

References

参考资料