zoukankan      html  css  js  c++  java
  • 发布订阅模式

    1、什么是发布订阅模式?

    发布订阅模式,在我们生活中是非常常见的一种,比如我们常见的微信公众号订阅号,被订阅的公众号作者会将更新的文章发送给每个订阅者,再比如我们找中介买房子,告诉了中介我们的需求(订阅),然后中介手上有了适合的房源后,将信息发送给所有订阅的人(发布)等。

    2、看一个最简单发布订阅的代码例子

    let e = {
        arr:[],
        on(fn){
            this.arr.push(fn);
        },
        emit(){
            this.arr.forEach(fn=>fn());
        }
    }
    e.on(()=>{
       console.log("haha") 
    })
    e.on(()=>{
       console.log("是不是傻") 
    })
    function sendMsg(){
        e.emit()
    }
    sendMsg()

    我们定义了一个对象e,它有两个方法,on:用于接收订阅的事件,将事件存储在变量arr中,emit:用于遍历所有订阅的事件,并执行

    然后在调用方法sendMsg()的时候,执行e.emit()方法。

    还有前面我们使用after函数读取文件的例子,同时去读取文件,当两个文件读取完成之后,需要打印出两个文件的内容,这个也可以使用我们的发布订阅来完成。代码如下:

    const e = {
        arr:[],
        on(fn){
            this.arr.push(fn);
        },
        emit(){
            this.arr.forEach(fn=>fn());
        }
    }
    
    let info = {};
    
    e.on(()=>{console.log("我读完了")})
    e.on(()=>{
        if(Object.keys(info).length==2){
            console.log(info);
        }
    });
    
    fs = require("fs");
    fs.readFile("name.txt","utf8",(err,data)=>{
        info["name"]=data;
        e.emit();
    })
    fs.readFile("age.txt","utf8",(err,data)=>{
        info["age"]=data;
        e.emit();
    })

    上面这个例子中,我先订阅了两个方法,一个函数作用是输出我读完了,一个函数用于判断当前存储文件内容的对象是否有两项,有的话,说明已经读完了。然后让fs分别去读取各自的文件name.txt和age.txt,每次读取完成都调用发布函数e.emit(),然后去轮流执行前面订阅的两个方法

    3、发布订阅模式的优点

    1)发布订阅模式最大的优点就是,解耦。

      发布订阅模式可以取代对象之间的硬编码的通知机制。一个对象不用再显示的调用另一个对象的某个接口。且当有新的订阅者出现时,发布者不用修改任何代码,同样发布者发生改变时,之前的订阅者也不会受到影响。

      举一个生活中的实例,比如我和朋友一起开了一个店,雇佣了一个店员帮我们看店,然后我们想要了解店里的经营情况,所以希望店员每成交一个订单就给我们分别发一条短信通知。

      要实现这个功能,我们需要分三步走:

        a)需要定义一个发布者,如例子中的店员

        b)发布者需要一个缓存列表,用于存放回调函数,以便通知订阅者

        c)发布者发布消息的时候,需要遍历缓存列表,依次触发订阅函数

    const employee = {
        employer:[], // 用于存放我和朋友的回调函数
        listen(fn){
            this.employer.push(fn)
        },
        trigger(...args){
            this.employer.forEach(fn=>fn(...args))
        }
    }
    
    employee.listen((salesAmount)=>{
        console.log("我:",salesAmount);  // 我:800
    })
    employee.listen((salesAmount)=>{
        console.log("朋友: ", salesAmount);  // 朋友:800
    })
    
    employee.trigger(800);

    上面就实现了一个简单的发布订阅,但是过了一段时间后,店里生意变得越来越好,每天的订单越来越多,我和朋友每天会收到很多很多短信,所以希望将模式修改一下,我和朋友决定分工合作,我收取所有订单金额大于200的订单信息,朋友接受所有订单金额小于200的订单信息。要实现这个功能,我们需要在订阅的时候,传递一个参数key,说明我们的需求,然后店员在发布的时候,也传递对应的key进行区分,代码如下:

    const employee = {
        employer:[],  // 存放订阅信息的缓存列表
        listen(key, fn){  // 订阅消息
            if(this.employer.indexOf(key)==-1){
                this.employer[key] = [];  // 如果还没有订阅过此类信息,给此类信息创建一个缓存列表
            }
            this.employer[key].push(fn);  // 订阅的消息,添加到缓存列表中
        },
        trigger(){  // 发布消息
            let key = Array.prototype.shift.call(arguments);  // 取出消息类型 lt200;gt200
            let fns = this.employer[key]  // 取出对应的方法
            if(!fns || fns.length==0){  
                return;
            }
            fns.forEach(fn=>{
                fn(arguments);  // 执行订阅方法(这里的arguments是已经去掉了消息类型的剩余参数)
            })
        }
    }
    employee.listen("lt200",(salesAmount)=>{  // 订阅
        console.log("朋友: ",salesAmount);
    })
    employee.listen("gt200",(salesAmount)=>{
        console.log("我:",salesAmount);
    })
    employee.trigger("lt200", 80);  // 发布
    employee.trigger("gt200",600);

     这样,我和朋友就可以按照自己的需要分别收到信息了。但是有一天朋友觉得,天天收那么多信息,太麻烦了,不想再收到信息了,怎么办呢?聪明的你肯定想到了,既然有订阅,就应该有取消订阅啊,所以接下来,我们需要给对象添加一个取消订阅的方法

    employee.cancelListen = function(key, fn){     // 取消订阅
        let func = this.employer[key];
        if(!func){    // 说明对应的key没有人订阅
            return false;
        }
        if(!fn){    // 取消订阅的人没有传递需要取消哪个订阅,则将当前key下对应的所有订阅全部取消
            fn.length = 0;
        }
       for(let l=func.length-1;i>=0;l--){
         if(fn==func[l]){
           func.splice(l,1)
         }
       }
    }
    function a(...args){
      console.log("我",...args)
    }
    employee.listen("gt200",a)
    console.log(employee.trigger("gt200",900))  // 我900
    employee.cancelListen("gt200",a)
    console.log(employee.trigger("gt200", 900))  // 此时没有输出,因为订阅已经被取消了

     但是我们的程序里面经常会有这种情况,由于ajax异步,可能在程序还没有订阅的时候,发布程序已经执行了,所以我们需要先将发布事件暂存起来,等到有人来订阅的时候,再执行。

    const event = {    // 订阅
        cache:{},
        stackFn:{},
        listen(key, fn){
            if(!this.cache[key]){
                this.cache[key] = [];
            }
            this.cache[key].push(fn);
            let stackArgs_ = this.stackFn[key];
            if(!stackArgs_){
                return;
            }
            stackArgs.forEach(args,=>{
                fn(...args);
            })
        },
        trigger(){
            let key = Array.prototype.shift.call(arguments);// 取出第一个参数
            let fns = this.cache[key];
            if(!fns || fns.length==0){
                if(!this.stackFn[key]){
                    this.stackFn[key] = [];
                }
                this.stackFn[key].push(arguments);
                return;
            }
            fns.forEach(fn_=>{
                fn_(...arguments)
            })
        }
    }
  • 相关阅读:
    PyQt5库结构
    PyQt5
    QObject
    os.listdir()
    os.getcwd()
    os.chdir()
    os.access()
    __name__
    php 学习笔记之关于时区的那点事
    php 学习笔记之搭建开发环境(mac版)
  • 原文地址:https://www.cnblogs.com/fiona-zhong/p/11447875.html
Copyright © 2011-2022 走看看