设计模式之发布订阅

发布订阅又称观察者模式,即定义一种一对多的方式,当发布者更新一种数值,多个订阅者收到相同的数值更替。发布订阅广泛用于异步编程中。

1. 中间件(单例模式)

要使用发布-订阅 模式,我们需要使用一个对象来作为发布-订阅的中间件,并用单例模式 进行管控,避免之后的开发进行了覆盖

/**
 * 以下代码用 typescript
 */
class Bus {
    static bus:any;
    pub:Object; // 存储所有的事件
    constructor(){
        if(!Bus.bus){
            Bus.bus = this
        }
        return Bus.bus
    }
    static getInstance(){
        if(!this.bus){
            this.bus = new Bus();
        }
        return this.bus;
    }
    /**
     * 订阅事件,与发布事件通过 key 作为信道
     * @param key 
     * @param fun 
     */
    public addListen(key:string,fun:Function){
        if(!this.pub.hasOwnProperty(key)){
            this.pub[key] = []
        }
        this.pub[key].push(fun)
    }

    /**
     * 移除事件
     * @param key 
     * @param fun 
     */
    public removeListen(key:string,fun:Function){
        if(!this.pub.hasOwnProperty(key)){
            throw new Error('未注册事件')
        }
        this.pub = this.pub[key].filter((fn:Function)=>{
            return fn !== fun
        })
    }

    /**
     * 发布事件
     * @param key 
     */
    public emit(key:string,...args:any[]){
        if(!this.pub.hasOwnProperty(key)){
            throw new Error('未注册事件')
        }

        let fns = this.pub[key];
        fns.forEach((fn:Function)=> {
            fn.apply(null,args)
        });
    }
}

let bus1 = new Bus();
let bus2 = new Bus();

console.log(bus1 === bus2); // true

2. 发布者

/**
 * 发布者
 */
const pub = {
    a : 1,
    add :function(){ bus1.emit('numchange', this.a+1 )}
}

我们构建一个 pub 对象,给它添加一个 add 方法,当调用它的时候,将它自己的 a 值加 1 进行发布,信道是 numchange

3. 订阅者

/** 
 * 订阅者1
 */
const sub1 = bus1.addListen('numchange',(num:Number)=>{
    console.log(`sub1 收到的 a 是 ${num}`)
})

/**
 * 订阅者2
 */
const sub2 = bus2.addListen('numchange',(num:Number)=>{
    console.log(`sub2 收到的 a 是 ${num}`)
})

我们构建两个订阅者,都订阅 numchange 这个信道

4. 发布

pub.add();

//true
//sub1 收到的 a 是 2
//sub2 收到的 a 是 2

5. 完整代码(经过转义)

/**
 * 以下代码用 typescript 撰写
 */
var Bus = /** @class */ (function () {
    function Bus() {
        this.pub = {}
        if (!Bus.bus) {
            Bus.bus = this;
        }
        return Bus.bus;
    }
    Bus.getInstance = function () {
        if (!this.bus) {
            this.bus = new Bus();
        }
        return this.bus;
    };
    /**
     * 订阅事件,与发布事件通过 key 作为信道
     * @param key
     * @param fun
     */
    Bus.prototype.addListen = function (key, fun) {
        if (!this.pub.hasOwnProperty(key)) {
            this.pub[key] = [];
        }
        this.pub[key].push(fun);
    };
    /**
     * 移除事件
     * @param key
     * @param fun
     */
    Bus.prototype.removeListen = function (key, fun) {
        if (!this.pub.hasOwnProperty(key)) {
            throw new Error('未注册事件');
        }
        this.pub = this.pub[key].filter(function (fn) {
            return fn !== fun;
        });
    };
    /**
     * 发布事件
     * @param key
     */
    Bus.prototype.emit = function (key) {
        var args = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            args[_i - 1] = arguments[_i];
        }
        if (!this.pub.hasOwnProperty(key)) {
            throw new Error('未注册事件');
        }
        var fns = this.pub[key];
        fns.forEach(function (fn) {
            fn.apply(null, args);
        });
    };
    return Bus;
}());
var bus1 = new Bus();
var bus2 = new Bus();
console.log(bus1 === bus2);
/**
 * 发布者
 */
var pub = {
    a: 1,
    add: function () { bus1.emit('numchange', this.a + 1); }
};
/**
 * 订阅者1
 */
var sub1 = bus1.addListen('numchange', function (num) {
    console.log("sub1 \u6536\u5230\u7684 a \u662F " + num);
});
/**
 * 订阅者2
 */
var sub2 = bus2.addListen('numchange', function (num) {
    console.log("sub2 \u6536\u5230\u7684 a \u662F " + num);
});
pub.add();

发布订阅在 react 框架中使用

发布订阅在 React 中常用于组件间的通信,其余方法还有 props,useContext ,redux

中间件

class Bus {
  author: string;
  // 监听方法
  listen: any;
  constructor() {
    this.author = 'siroi';
    this.listen = {};
  }

  // 订阅者订阅消息
  addListen = (key:string, fn:any)=>{
    if (!this.listen[key]) {
        this.listen[key] = [];
      }
      this.listen[key].push(fn);
  }

  //移除订阅
  removeListen = (key:string,fn:any)=>{
    const fns = this.listen[key];
    if (!fns || fns.length === 0) return;
    if (!fn) {
      this.listen[key] = [];
    } else {
      for (let l = fns.length - 1; l >= 0; l--) {
        if (fn === fns[l]) {
          fns.splice(l, 1);
        }
      }
    }
  }

  //发布订阅
  emit(key:string,...args:any[]) {
    const fns = this.listen[key];
    if (!fns || fns.length === 0) {
      return false;
    }
    fns.forEach((fn:any) => {
      fn.apply(this, args);
    });
  }
}

export default new Bus();

发布者

import { useEffect, useState } from 'react';
import Bus from './Obj';

const FaBu = () => {
    const [num, setNum] = useState(0);

    useEffect(() => {
        console.log(num)
        Bus.emit('clickFaBu', num);
    }, [num]);

    const clickDiv = () => {
        setNum(num + 1);
    };
    return (
        <>
            <div>子组件1</div>
            <div onClick={clickDiv}>点我加一</div>
        </>
    );
};

export default FaBu;

订阅者

import { useEffect, useState } from "react";
import bus from './Obj';
import init from "./pub";
const DingYue = ()=>{
    const [num,setNum] = useState(0);
    useEffect(()=>{
        init();
        bus.addListen('clickFaBu',(num:number)=>{
            setNum(num)
        })
    },[])

    return (
        <><div>
            订阅者订阅消息{num}
        </div>
        </>
    )
}

export default DingYue

With great power there must come great responsibility.