观察者模式与发布订阅模式

前言

我的任督二脉终于被打通了,现在该你了

区别

观察者模式

  1. 就2个角色:观察者和被观察者(重要)
  2. 明确知道状态源,明确知道对方是谁
  3. 一对多关系

发布订阅模式

  1. 有3个角色:发布者,订阅者和发布订阅中心(重要)
  2. 发布者和订阅者不知对方存在
  3. 多对多关系

观察者模式

  1. 观察者盯着被观察者看
  2. 被观察者将有权限添加,删除和通知观察者(这是被观察者至少需要的三个基本方法)
  3. 只要被观察者 发动 通知观察者方法时,观察者列表里的所有观察者就可以接收到消息

(一)实现

1. 被观察者(有一个观察者列表,可以添加,移除和通知观察者)

class Subject {

    constructor(){
        // 定义一个观察者列表用来存放观察者们
        this.observerList = [];
    }
    // 添加观察者    
    addObserver(observer){
        this.observerList.push(observer);
    }
    // 移除观察者
    removeObserver(observer){
        this.observerList.filter(o => o.name !== observer.name)
    }
    // 通知观察者
    notifyObserver(message){
        // 我理解的 notified 是观察者自己定义的如何通知自己,
        // 每个观察者的notified内部实现可能是不一样的
        this.observerList.forEach(o => o.notified(message))
    }

}

2. 观察者(可以申请加入被观察者的列表,自定义通知方法)

class Observer {

    constructor(name, subject){
        this.name = name;
        if(subject){
            // 如果构建时就有了被观察者(subject),
            // 则申请让被观察者将自己加入列表
            subject.addObserver(this)
        }
    }
    notified(message){
        // TODO 观察者拿到订阅到的消息后要做的事
        console.log(this.name, '拿到了消息',message)
    }

}

3. 使用

// 生成一个被观察者
    const subject = new Subject();

// 生成一个自带subject的观察者
    const A_observer = new Observer('A',subject);

// 通知
    subject.notifyObserver('咳咳');
// A拿到了消息咳咳



// 生成一个没有观察对象subject的观察者
    const B_observer = new Observer('B',null);

// 申请观察subject
    subject.addObserver(B_observer);
// 移除
    // subject.removeObserver(A_observer);
// 通知
    subject.notifyObserver('哈哈哈')    
// A拿到了消息哈哈哈
// B拿到了消息哈哈哈
// 当然也可以写一个新的class Observer来实现不同的notified

参考https://juejin.cn/post/6978728619782701087

(二)升级版实现

1. 被观察者

class newSubject {
    constructor(){
        this.observerList = [];
    }
    // 移除观察者
    removeObserver(observer){
        this.observerList.filter(o => o.name !== observer.name)
    }
__________修改___________________________________________________________________
    // 添加观察者
    addObserver(name, func){
        this.observerList.push({ name:name, callback:func});
    }
    // 通知观察者
    notifyObserver(message){
        // 我理解的 notified 是观察者自己定义的一个回调函数,
        // 即被观察者(observer)给观察者(subject)一个notified函数,告诉subject有消息就用这个函数通知自己,
        // 每个观察者的notified内部实现可能是不一样的
            // this.observerList.forEach(o => o.notified(message))
        // 通过对象的解构赋值,这样就可以不用规定死observer内部通知方法的名称为notified,更加灵活
        this.observerList.forEach(({name,callback}) => callback(message))
    }
}

2. 使用

    const new_subject = new newSubject();
    //添加观察者C时,将new_subject的观察者列表修改为统一的 { name, callback } 格式
    new_subject.addObserver('C',(message)=>{
        // notified
        console.log('hello, C', message)
    })
    //通知
    new_subject.notify(‘升级啦’)

 //‘hello, C升级啦’

参考http://dennisgo.cn/Articles/DesignPatterns/PubSub.html

一旦调用了subject.notify , 所有列表内的观察者都会收到通知,而且收到的是同样的消息

发布订阅模式

  1. 如之前所说,发布订阅模式有3个角色,发布者,订阅者和发布订阅中心,
  2. 发布订阅核心是基于发布订阅中心来建立联系
  3. 发布者无需关心订阅者有哪些,订阅者也无需关心有哪些发布者,可以表达对一个或多个主题的兴趣,只接收感兴趣的消息

让我们来想象一下邮件系统,你可以作为订阅者订阅某个网站的通知,邮件系统在其中充当发布订阅中心的角色,而发布者则是你订阅的网站。

整个链路是从你的订阅开始,你的订阅动作是在某个你想订阅的网站填入自己的邮箱(在主题中添加订阅者列表),并在邮箱内记录这个网站信息(添加主题),后续当网站有内容更新时,邮件系统会及时接收到并向你发送邮件。
————————————————引用自掘金 https://juejin.cn/post/6978728619782701087

(一)实现

1. 发布订阅中心 (记住订阅是在为主题添加订阅者,发布是在执行订阅者的通知方法)

// 发布订阅中心
class PubSub {
    constructor() {
        this.sublist = {}; 
        //为什么观察者模式要用数组,这里却要用对象?想想平时在设置变量时,一对多的关系一般用数组来表示,而多对多的关系一般都用
        //数组对象来表示是不是
        //订阅列表,每个主题对应对象中的一个数组,键为主题名,值为不同订阅者的通知方式(可以理解为该主题下的订阅者列表)
        //为每个主题添加了多个订阅者,存放在数组(订阅者列表)中
        /**
         * this.sublist = {
         *     theme1:[notify_1_1, notify_1_2], // notify_1_1订阅了theme1,notify_1_2订阅了theme1,
         *     theme2:[notify_1_1, notify_2_2], // notify_1_1订阅了theme2,notify_2_2订阅了theme2,
         *     ......
         * }
         * **/
    }

    // 订阅
    subscribe(theme, notify_fn) {
        // 如果this.sublist[theme]为null,先将其定义为数组, 有了主题后,直接push订阅者的通知方法即可
        (this.sublist[theme] || (this.sublist[theme] = [])).push(notify_fn);
    }

    // 发布
    publish(theme, ...args) {
        if(!this.sublist[theme]){
            // 如果该主题不存在通知方法(即订阅者)
            return;
        }
        // 发布某个主题时,该主题的所有订阅者都可以接到通知
        this.sublist[theme].map(notify_fn => notify_fn(args))
    }

}

2. 使用

 const pubsub = new PubSub();

// 订阅者
 function user1(content) {
    console.log('用户1订阅 ',content)
 }
 function user2(content) { 
    console.log('用户2订阅 ',content)
 }
 function user3(content) {
    console.log('用户3订阅 ',content)
 }

// 添加订阅
 pubsub.subscribe('theme1',(content)=>{
    // 以后如果发布订阅中心pubsub发布了theme1的内容,立即通知执行user1方法
    user1(content)
 })
 pubsub.subscribe('theme1',(content)=>{
    user2(content)
 })
 pubsub.subscribe('theme2',(content)=>{
    user3(content)
 })

// 发布(发布即起到了通知的作用,只有发布订阅中心执行发布方法,订阅者才能收到消息,因为publish执行了notify_fn)
 pubsub.publish('theme1', '主题1内容');
 pubsub.publish('theme2', '主题2内容');

// 结果
 // 用户1订阅 主题1内容
 // 用户2订阅 主题1内容
 // 用户3订阅 主题2内容

参考
https://extremej.itscoder.com/different_between_observe_and_publish/
http://dennisgo.cn/Articles/DesignPatterns/PubSub.html
https://juejin.cn/post/6844903842501378055
https://juejin.cn/post/6844903850105634824

实际场景

  1. Node.js中自带的EventEmiter模块
  2. Vue.js中数据响应式的实现
  3. watch、watcher、observe、observer、listen、listener、dispatch、trigger、emit、on、event、eventbus、EventEmitter这类单词出现的地方,很有可能是在使用观察者模式或发布订阅模式
  4. 例如on (subscribe),和 emit(publish)

总结

1. 角色对象

  • 观察者模式角色:观察者和被观察者
  • 发布订阅模式角色:发布者,订阅者和发布订阅中心

2. 实现

  • 观察者模式:
    • 被观察者(有一个观察者列表,可以添加,移除和通知观察者)
    • 观察者(可以申请加入被观察者的列表,自定义通知方法)
  • 发布订阅模式:
    • 发布订阅中心 (记住订阅是在为主题添加订阅者,发布是在执行订阅者的通知方法)

3. 特定消息接收

  • 发布订阅模式可以自定义需要接受的通知,定制主题
  • 观察者模式相当于是无差别广播,一旦调用了notify通知 , 所有列表内的观察者都会收到通知,而且收到的是同样的消息

4. 映射关系

  • 观察者模式:普遍一对多
  • 发布订阅模式:普遍多对多

5. 对象间关系

  • 观察者模式:明确知道状态源,双方直接联系
  • 发布订阅者模式:双方通过发布订阅中心关联
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值