是时候拿出高级的技术了——观察者模式

1,378 阅读4分钟

背景介绍

这是设计模式系列的第四节,学习的是patterns.dev里设计模式中观察者模式内容,由于是资料是英文版,所以我的学习笔记就带有翻译的性质,但并不是翻译,记录的是自己的学习过程和理解

第一节:高并发造成的数据统计困难?看我单例模式一招制敌

第二节:JS和迪丽热巴一样有专业替身?没听过的快来补补课...

第三节:还在层层传递props?来学学非常实用的供应商模式吧

第四节:都知道JavaScript原型,但设计模式里的原型模式你会用吗?

第五节:React Hooks时代,怎么实现视图与逻辑分离呢?

写在前面

观察者模式,顾名思义就像监视者一直在观察某个人的一举一动,在编程里就是观察者通过订阅获得事件通知权限,事件发生时消息通知所有已注册的观察者。观察者模式是一个很经典,应用很广泛的模式,常见的有新手消息邮件订阅RSS Feeds等都是经典实用场景。

人通过观察可以主动发现目标对象的变化,但是观察者模式则是需要被动接受通知!

极简释义

观察者通过订阅获得事件通知权限,事件发生时发消息通知所有已注册的观察者

正文开始

在程序中观察者模式具体由哪几部分构成呢?

  • 观察者:
  • 订阅事件:
  • 取消订阅事件:
  • 消息通知:

通过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方法通知全部的观察者。

然后我们可以在需要的地方使用观察者模式:

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>
  );
}

通过点击Button和Switch分别发布不同的消息,通过subscribe方法订阅所有事件对象会被执行;

image.png

在线示例

观察者模式十分有用,特别是异步的,基于事件的数据处理:

  • 下载完成时通知某些组件;
  • 留言通知所有成员。

经典案例学习 ——— RxJs

RxJs是一个非常流行的js库:

ReactiveX将Observer模式Iterator模式以及函数式编程结合起来,从而高效地管理事件序列。

在RxJs中,我们创建观察者模式,并订阅某个事件;下面我们来看一个检测用户是否拖拽Dom的小例子:

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里有很多内置的特性案例使用到了观察者模式,有兴趣的可以看看源码!

优缺点分析

优点:

观察者模式有很多优点,大概总结下:

  • 分离关注点SOC (separation of concerns);
  • 满足单一指责原则;
  • 解耦合,观察者和事件对象解耦合;

被观察的对象通常是一些交互事件或异步事件,观察者通常处理接收到的数据。

缺点:

观察者模式如果变得太复杂臃肿,在通知所有订阅者时可能会导致性能问题。

同义辨析————观察者模式和订阅发布模式

观察者模式应用于Observable对象,观察者和事件消息直接交互

观察者模式就像你要跳槽,心中有了目标公司,你就会关注目标公司的招聘信息,当发现有新招聘时就会行动;

订阅发布模式应用于事件消息发布者订阅者完全解耦,通过调度中心交互;

订阅发布模式则是你要跳槽,你在招聘网站修改在职状态,然后很多公司在招聘网站上发布招聘信息,然后招聘网站把这些招聘信息推送到你面前。