观察者设计模式
一个目标对象维持着一系列依赖于它的对象,将有关状态的任何变更自动通知观察者们。在观察者模式中,观察者需要直接订阅目标对象,观察者与目标对象之间有一定的依赖关系。
有4个重要的概念
1 | // 目标对象 |
生活例子(发布订阅模式)
人的日常生活离不开各种人际交涉,比如你的朋友有很多,这时候你要结婚了,要以你为发布者,打开你的通讯录,挨个打电话通知各个订阅者你要结婚的消息。抽象一下,实现发布-订阅模式需要:
- 发布者(你)
- 缓存列表(通讯录,你的朋友们相当于订阅了你的所有消息)
- 发布消息的时候遍历缓存列表,依次触发里面存放的订阅者的回调函数(挨个打电话)
- 另外,回调函数中还可以添加很多参数,,订阅者可以接收这些参数,比如你会告诉他们婚礼时间,地点等,订阅者收到消息后可以进行各自的处理。
1 | let yourMsg = {}; |
以上就是一个简单的发布-订阅的实现,但是我们会发现订阅者会收到发布者发布的每一条信息,如果李四比较阴暗,不想听到你结婚的消息,只想听到你的坏消息,比如你被开除了,他就心里高兴。这时候我们就需要加一个key,让订阅者只订阅自己感兴趣的消息。代码:
1 | let yourMsg = {}; |
你需要发布消息,同样的所有的人都有朋友圈,也都需要发布消息,因此我们有必要把发布-订阅的功能提取出来,放在一个单独的对象内,谁需要谁去动态安装发布-订阅功能(installEvent函数实现了动态安装发布-订阅功能)。代码:
1 | var event = { |
有时间我们需要取消订阅的事件,比如李四是你的好朋友,但是因为一件事情,你俩闹掰了,你把他从你的通讯录中给删除掉了,这里我们给event增加一个remove方法;
发布订阅完整代码(解决命名空间问题,删除订阅问题):
1 | var Event = (function(){ |
发布-订阅设计模式
在发布-订阅模式,消息的发送方,叫做发布者(publishers),消息不会直接发送给特定的接收者,叫做订阅者。
意思就是发布者和订阅者不知道对方的存在。需要一个第三方组件,叫做信息中介,它将订阅者和发布者串联起来,它过滤和分配所有输入的消息。换句话说,发布-订阅模式用来处理不同系统组件的信息交流,即使这些组件不知道对方的存在。
如买卖房子例子:
生活中的买房,卖房,中介就构成了一个发布订阅者模式,买房的人,一般需要的是房源,价格,使用面积等信息,
他充当了订阅者的角色中介拿到卖主的房源信息,根据手头上掌握的客户联系信息(买房的人的手机号),通知买房的人,
他充当了发布者的角色卖主想卖掉自己的房子,就需要告诉中介,把信息交给中介发布
我用下图表示这两个模式最重要的区别:
我们把这些差异快速总结一下:
在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。
尽管它们之间有区别,但有些人可能会说发布-订阅模式是观察者模式的变异,因为它们概念上是相似的。
vue中的应用
Vue会遍历实例的data属性,把每一个data都设置为访问器,然后在该属性的getter函数中将其设为watcher,在setter中向其他watcher发布改变的消息。
这样,配合发布/订阅模式,改变其中的一个值,会发布消息,所有的watcher会更新自己,这些watcher也就是绑定在dom中的显示信息。
从而达到改变浏dom,在浏览器中实时变化的效果,代码如下:1
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//遍历传入实例的data对象的属性,将其设置为Vue对象的访问器属性
function observe(obj,vm){
Object.keys(obj).forEach(function(key){
defineReactive(vm,key,obj[key]);
});
}
//设置为访问器属性,并在其getter和setter函数中,使用订阅发布模式。互相监听。
function defineReactive(obj,key,val){
//这里用到了观察者(订阅/发布)模式,它定义了一种一对多的关系,让多个观察者监听一个主题对象,这个主题对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。
//实例化一个主题对象,对象中有空的观察者列表
var dep = new Dep();
//将data的每一个属性都设置为Vue对象的访问器属性,属性名和data中相同
//所以每次修改Vue.data的时候,都会调用下边的get和set方法。然后会监听v-model的input事件,当改变了input的值,就相应的改变Vue.data的数据,然后触发这里的set方法
Object.defineProperty(obj,key,{
get: function(){
//Dep.target指针指向watcher,增加订阅者watcher到主体对象Dep
if(Dep.target){
dep.addSub(Dep.target);
}
return val;
},
set: function(newVal){
if(newVal === val){
return
}
val = newVal;
//console.log(val);
//给订阅者列表中的watchers发出通知
dep.notify();
}
});
}
//主题对象Dep构造函数
function Dep(){
this.subs = [];
}
//Dep有两个方法,增加订阅者 和 发布消息
Dep.prototype = {
addSub: function(sub){
this.subs.push(sub);
},
notify: function(){
this.subs.forEach(function(sub){
sub.update();
});
}
}