观察者模式
# 观察者模式和发布订阅模式
观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心。
观察者模式中观察者和被观察者,是松耦合
发布订阅模式中发布者和订阅者,是完全解耦的
# 区别总结
- 角色不同
观察者模式需要两种角色,观察者和被观察者
发布订阅模式需要三种角色,发布者、事件中心和订阅者
- 耦合度不同
观察者模式中观察者和被观察者,是松耦合
发布订阅模式中发布者和订阅者,是完全解耦的
# 观察者模式
观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯
概念:观察者模式指的是一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进行更新。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
观察者模式里,只有两个角色 观察者 + 被观察者
# 优缺点
优点:
观察者和被观察者是抽象耦合的。
建立一套触发机制。
缺点:
如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
# 使用场景
一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
一个对象必须通知其他对象,而并不知道这些对象是谁。
需要在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……,可以使用观察者模式创建一种链式触发机制。
# ES6 代码实现
在观察者模式中,Subject 对象拥有添加、删除和通知一系列 Observer 的方法等等,而 Observer 对象拥有更新方法等等。
// 定义一个主体对象
class Subject {
constructor() {
this.Observers = [];
}
add(observer) {
//添加
this.Observers.push(observer);
}
remove(observer) {
//移除
this.Observers.filter((item) => item === observer);
}
notify() {
this.Observers.forEach((item) => {
item.update();
});
}
}
//定义观察着对象
class Observer {
constructor(name) {
this.name = name;
}
update() {
console.log(`my name is:${this.name}`);
}
}
//测试
let sub = new Subject();
let obs1 = new Observer("leaf111");
let obs2 = new Observer("leaf222");
sub.add(obs1);
sub.add(obs2);
sub.notify();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
上述代码中,我们创建了 Subject 对象和两个 Observer 对象,当有关状态发生变更时则通过 Subject 对象的 notify 方法通知这两个 Observer 对象,这两个 Observer 对象通过 update 方法进行更新。
# 发布订阅模式
发布订阅模式指的是希望接收通知的对象(Subscriber)基于一个主题通过自定义事件订阅主题,被激活事件的对象(Publisher)通过发布主题事件的方式通知各个订阅该主题的 Subscriber 对象。
# 来源
24 种基本的设计模式中并没有发布订阅模式,它只是观察者模式的一个别称。
但是经过时间的沉淀,已经独立于观察者模式,成为另外一种不同的设计模式。
在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。
# ES6 代码实现
//声明类
class EventBus {
// 事件存储中心
constructor() {
this.eventList = {}; //创建对象收集事件
}
// 订阅:订阅者注册事件到调度中心
$on(eventName, fn) {
this.eventList[eventName]
? this.eventList[eventName].push(fn)
: (this.eventList[eventName] = [fn]);
}
// 发布:发布者发布事件到调度中心,调度中心处理代码
$emit(eventName) {
if (!eventName) throw new Error("请传入事件名");
//获取订阅传参
const data = [...arguments].slice(1);
if (this.eventList[eventName]) {
this.eventList[eventName].forEach((i) => {
try {
i(...data); // 轮询事件
} catch (e) {
console.error(e + "eventName:" + eventName); // 收集执行时的报错
}
});
}
}
//执行一次
$once(eventName, fn) {
const _this = this;
function onceHandle() {
fn.apply(null, arguments);
_this.$off(eventName, onceHandle); //执行成功后取消监听
}
this.$on(eventName, onceHandle);
}
//取消订阅
$off(eventName, fn) {
//不传入参数时取消全部订阅
if (!arguments.length) {
return (this.eventList = {});
}
//eventName传入的是数组时,取消多个订阅
if (Array.isArray(eventName)) {
return eventName.forEach((event) => {
this.$off(event, fn);
});
}
//不传入fn时取消事件名下的所有队列
if (arguments.length === 1 || !fn) {
this.eventList[eventName] = [];
}
//取消事件名下的fn
this.eventList[eventName] = this.eventList[eventName].filter(
(f) => f !== fn
);
}
}
const event = new EventBus();
let b = function(v1, v2, v3) {
console.log("b", v1, v2, v3);
};
let a = function() {
console.log("a");
};
event.$once("test", a);
event.$on("test", b);
event.$emit("test", 1, 2, 3, 45, 123);
event.$off(["test"], b);
event.$emit("test", 1, 2, 3, 45, 123);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73