这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
一、简介
观察者模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
观察者模式有两个角色:
- 目标对象
- 观察者
观察者模式的简单流程:
- 将观察者注册到目标对象的观察者列表中
- 目标对象进行 通知(notify) 操作
- 目标对象调用观察者的 更新(update) 方法,将通知的信息传递给观察者
联系实际:
报纸期刊的订阅。当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是上面文章开头所说的“一对多”的依赖关系。
具体 | 一般 |
---|---|
在报社留下你的电话 | 在目标对象中注册观察者 |
报社通知订阅者消息 | 在目标对象通过对观察者的引用调用相应的方法 |
发布-订阅模式
在软件架构中,发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
在发布者和订阅者之间存在第三个组件,称为事件中心或事件通道,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。
发布-订阅模式有三个角色:
- 事件中心
- 订阅者
- 发布者
发布-订阅模式的简单流程:
- 订阅者需要向事件中心订阅指定的事件
- 发布者需要向事件中心发布指定的事件
- 事件中心通知订阅者
二、简单实现
观察者模式
class Subject{//目标类
constructor(){
this.subjectList = []; //观察者列表
}
add(observer){ //注册观察者
this.subjectList.push(observer)
}
notify(context){ //发出通知
this.subjectList.forEach(observer => observer.update(context));
}
remove(fn){ //移除观察者
this.subjectList.splice(this.subjectList.indexOf(fn),1)
}
}
class Observer{ //观察者
update(data){
console.log(`目标对象发出通知:${data}`)
/**
* 相应的业务逻辑
*/
}
}
var Subject1 = new Subject()//目标1
var Subject2 = new Subject()//目标2
var Observer1 = new Observer()//观察者1
var Observer2 = new Observer()//观察者2
Subject1.add(Observer1);//注册
Subject1.add(Observer2);//注册
Subject2.add(Observer1);//注册
Subject1.notify('test1')//发出通知
Subject2.notify('test2')//发出通知
复制代码
发布-订阅模式
class PubSub {
constructor() {
// 维护事件及订阅行为
this.events = {};
}
/**
* 订阅事件
*/
subscribe(topic, callback) {
if (!this.events[topic]) {
this.events[topic] = [];
}
this.events[topic].push(callback);
}
/**
* 发布事件
* @param {String} topic 事件类型
* @param {Any} data 发布的内容
*/
publish(topic, data) {
// 取出对应主题的回调函数
const callbacks = this.events[topic];
if (callbacks) {
callbacks.forEach(cb => {
cb(data);
});
}
}
}
const pubSub = new PubSub();
pubSub.subscribe("open", (data) => {
console.log(data);
});
pubSub.publish("open", { open: true });
复制代码
三、二者的区别
这张非常经典的图非常清晰的描述了观察者模式和发布-订阅模式的区别
-
观察者模式中的目标和观察者是直接联系的,而发布-订阅模式中的订阅者和发布者中间是由事件中心来联系的。
观察者模式:目标和观察者是低耦合的,有很强的依赖关系。
发布-订阅模式:由于事件中心的存在使得订阅者和发布者是完全解耦的。
-
观察者要想订阅目标事件,由于没有事件中心,因此必须将自己添加到目标(Subject) 中进行管理;
目标在触发事件的时候,也无法将通知操作(notify) 委托给事件中心,因此只能亲自去通知所有的观察者。
-
从代码实现的角度,
观察者模式是面向目标和观察者编程的。
发布-订阅模式是面向调度中心编程的。
三、注意点
-
在给出的实例中,观察者模式中在观察者列表中添加的是观察者对象;而发布-订阅模式在事件中心中添加的是回调函数。个人理解观察者模式中可能更倾向于将通知信息返回到观察者自身,而发布-订阅模式可能更倾向于对相应发布事件的处理(通过回调函数)。
-
发布-订阅模式,对于不同的实现,订阅者和发布者不一定要是具体的类(上面有两种实现的方法)。
四、两个小问题?
1. 观察者模式和发布-订阅模式是同一个模式吗?
观察者模式与发布/订阅模式大概就跟番茄与圣女果的关系一样,剪不断理还乱。
有的人认为本质上是同一种模式,但就像上面所描述的,二者还是有一定的区别。
这里引用大佬的一段分析
其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。
但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。
或许可以认为————以前订阅发布模式是观察者模式的一种别称,但是发展至今,概念已经有了不少区别。
2. 这个案例是观察者模式还是发布-订阅模式?
// 主题类
class Dep {
constructor(callback) {
this.subs = []; // 主题的订阅者
this.callback = callback;
}
// 添加订阅者
addSub(sub) {
this.subs.push(sub);
return this;
}
// 主题更新通知---调用订阅者update,通知所有订阅者
notify() {
this.subs.forEach(item => item.update(this.callback));
}
}
// 订阅者
class Sub {
constructor(val) {
this.val = val;
}
update(callback) {
this.val = callback(this.val); // 执行订阅主题的函数
console.log('更新之后:', this.val);
}
}
// 发布者
class Pub {
constructor() {
this.deps = []; // 发布的主题列表
}
// 添加主题
addDep(dep) {
this.deps.push(dep);
}
// 移除主题
removeDep(dep) {
let index = this.deps.indexOf(dep);
if (index !== -1) {
this.deps.splice(index, 1);
return true;
} else {
return false;
}
}
// 发布主题
publish(dep) {
this.deps.forEach(item => item == dep && item.notify());
}
}
// 新建主题,给主题中加订阅者
let dep1 = new Dep(item => item * item);
dep1.addSub(new Sub(1)).addSub(new Sub(2)).addSub(new Sub(3));
// 新建发布者
let pub = new Pub();
// 添加主题
pub.addDep(dep1);
// 发布者发布,通知这个主题的所有订阅者更新
pub.publish(dep1);
// 输出结果
// 更新之后结果:1
// 更新之后结果:4
// 更新之后结果:9
复制代码