观察者模式与发布订阅模式
前言
我的任督二脉终于被打通了,现在该你了
区别
观察者模式
- 就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 , 所有列表内的观察者都会收到通知,而且收到的是同样的消息
发布订阅模式
- 如之前所说,发布订阅模式有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
实际场景
如
- Node.js中自带的EventEmiter模块
- Vue.js中数据响应式的实现
- watch、watcher、observe、observer、listen、listener、dispatch、trigger、emit、on、event、eventbus、EventEmitter这类单词出现的地方,很有可能是在使用观察者模式或发布订阅模式
- 例如on (subscribe),和 emit(publish)
总结
1. 角色对象
- 观察者模式角色:观察者和被观察者
- 发布订阅模式角色:发布者,订阅者和发布订阅中心
2. 实现
- 观察者模式:
- 被观察者(有一个观察者列表,可以添加,移除和通知观察者)
- 观察者(可以申请加入被观察者的列表,自定义通知方法)
- 发布订阅模式:
- 发布订阅中心 (记住订阅是在为主题添加订阅者,发布是在执行订阅者的通知方法)
3. 特定消息接收
- 发布订阅模式可以自定义需要接受的通知,定制主题
- 观察者模式相当于是无差别广播,一旦调用了notify通知 , 所有列表内的观察者都会收到通知,而且收到的是同样的消息
4. 映射关系
- 观察者模式:普遍一对多
- 发布订阅模式:普遍多对多
5. 对象间关系
- 观察者模式:明确知道状态源,双方直接联系
- 发布订阅者模式:双方通过发布订阅中心关联