设计模式 - 观察者模式

通过观察者模式,我们可以订阅某些对象(即观察者)到另一个对象(称为被观察者)。每当事件发生时,被观察者会通知其所有观察者!

可观察对象通常包含 4 个重要部分:

  • observers:每当发生特定事件时都会收到通知的观察者列表
  • subscribe():一种将观察者添加到观察者列表的方法
  • unsubscribe():一种从观察者列表中删除观察者的方法
  • notify():一种在特定事件发生时通知所有观察者的方法

让我们来创建一个 Observable!创建 Observable 的一种简单方法是通过使用 ES6 类。

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 通知,每当事件发生时都会显示!

从本质上讲,我们想要做的是以下几点:

每当用户调用 handleClick 或 handleToggle 函数时,这些函数都会在观察者上调用notify方法。notify 方法使用 handleClick 或 handleToggle 函数传递的数据通知所有订阅者!

首先,让我们创建 logger 和 toastify 函数。这些函数最终将从 notify 方法接收data

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 函数无法识别可观察对象:可观察对象还不能通知它们!为了让它们成为观察者,我们必须对可观察对象使用 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 函数!这些函数应调用可观察对象的 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 使用数据在观察者上调用notify方法,然后观察者通知订阅者:在本例中为 logger 和 toastify 函数。

每当用户与任一组件交互时,logger和 toastify 函数都会收到我们传递给 notify 方法的数据的通知!

// App.js

import React from "react";
import { Button, Switch, FormControlLabel } from "@material-ui/core";
import { ToastContainer, toast } from "react-toastify";
import observable from "./Observable";

function handleClick() {
  observable.notify("User clicked button!");
}

function handleToggle() {
  observable.notify("User toggled switch!");
}

function logger(data) {
  console.log(`${Date.now()} ${data}`);
}

function toastify(data) {
  toast(data, {
    position: toast.POSITION.BOTTOM_RIGHT,
    closeButton: false,
    autoClose: 2000
  });
}

observable.subscribe(logger);
observable.subscribe(toastify);

export default function App() {
  return (
    <div className="App">
      <Button variant="contained" onClick={handleClick}>
        Click me!
      </Button>
      <FormControlLabel
        control={<Switch name="" onChange={handleToggle} />}
        label="Toggle me!"
      />
      <ToastContainer />
    </div>
  );
}

尽管我们可以以多种方式使用观察者模式,但在处理基于事件的异步数据时,它可能非常有用。也许您希望某些组件在某些数据下载完成时收到通知,或者每当用户向留言板发送新消息时,所有其他成员都应收到通知。

个案研究

使用可观察模式的流行库是 RxJS。

ReactiveX 将 Observer 模式与 Iterator 模式以及函数式编程与集合相结合,以满足对管理事件序列的理想方式的需求。- RxJS

使用 RxJS,我们可以创建可观察对象并订阅某些事件!让我们看一下他们的文档中介绍的一个示例,该示例记录了用户是否在文档中拖动。

// index.js

import React from "react";
import ReactDOM from "react-dom";
import { fromEvent, merge } from "rxjs";
import { sample, mapTo } from "rxjs/operators";

import "./styles.css";

merge(
  fromEvent(document, "mousedown").pipe(mapTo(false)),
  fromEvent(document, "mousemove").pipe(mapTo(true))
)
  .pipe(sample(fromEvent(document, "mouseup")))
  .subscribe(isDragging => {
    console.log("Were you dragging?", isDragging);
  });

ReactDOM.render(
  <div className="App">Click or drag anywhere and check the console!</div>,
  document.getElementById("root")
);

RxJS 具有大量与可观察模式一起使用的内置功能和示例。

优点

使用观察者模式是强制执行关注点分离和单一责任原则的好方法。观察者对象与可观察对象没有紧密耦合,可以随时(解耦)。可观察对象负责监视事件,而观察者只是处理接收到的数据。

缺点

如果观察者变得过于复杂,则在通知所有订阅者时可能会导致性能问题。

  • 43
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值