做为非科班出身的前端er,每次听到设计模式都感觉很高大上,总感觉这些东西是造火箭原子弹用的,距离我们这些造螺丝钉很遥远。但是最近在做一个聊天消息的业务时,发现貌似用上发布订阅模式业务就很清晰了。创建一个消息类当作发布者,展示消息的函数是订阅者,发布者提供了注册、发布方法,订阅者注册后,每次调用发布方法修改数据时,订阅者函数自动更新数据。
class MsgList{//发布者
constructor (){
this.list = [];
this.fn = []
}
listen(fn){//注册
this.fn.push(fn)
}
add(text){//发布
this.list.push(text)
this.fn.map((item)=>{
item(this.list)
})
}
}
function show(msg){//订阅者
console.log(msg) //自动打印
}
var msg1 = new MsgList();
msg1.listen(show)
msg1.add('消息1')
msg1.add('消息2')
观察者模式与发布订阅模式类似。在此种模式中,一个目标物件在它本身的状态改变时主动发出通知,观察者收到通知从而使他们的状态自动发生变化。核心就是我(观察者)正在看着你(被观察者),看着你目不转睛...你只要改变我就自动改变。概念是不是很清晰了。但是观察者模式又跟发布订阅有些许不太一样的地方。
一、观察者模式
目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。
下面通过js来实现下观察者模式。首先是目标的构造函数,他有个数组,用于添加观察者。还有个广播方法,遍历观察者数组后调用他们的update方法:
class Subject{//目标类===被观察者
constructor(){
this.subjectList = [];//目标列表
}
add(fn){//注册
this.subjectList.push(fn)
}
notify(context){//发通知
var subjectCount = this.subjectList.length
for(var i=0; i < subjectCount; i++){
this.subjectList[i].update(context)
}
}
//取消注册
remove(fn){
this.subjectList.splice(this.subjectList.indexOf(fn),1)
}
}
class Observer{//观察者类==观察者
update(data){
console.log('updata +' + data)
}
}
var Subject1 = new Subject()//具体目标1
var Subject2 = new Subject()//具体目标2
var Observer1 = new Observer()//具体观察者1
var Observer2 = new Observer()//具体观察者2
Subject1.add(Observer1);//注册 //updata +test1
Subject1.add(Observer2);//注册 //updata +test1
Subject2.add(Observer1);//注册 //updata +test2
Subject1.notify('test1')//发布事件
Subject2.notify('test2')//发布事件
从上面代码可以看出来,先创建具体目标和具体观察者,然后通过add方法把具体观察者 Observer1、Observer2注册到具体目标中,目标和观察者是直接联系起来的,所以具体观察者需要提供update方法。在Subject1中发通知时,Observer1、Observer2都会接收通知从而更改状态。
二、 发布/订阅模式
观察者模式存在一个问题,目标无法选择自己想要的消息发布,观察者会接收所有消息。在此基础上,出现了
发布/订阅模式,在目标和观察者之间增加一个调度中心。订阅者(观察者)把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者(目标)发布该事件到调度中心,由调度中心统一调度订阅者注册到调度中心的处理代码。
class Public{//事件通道
constructor(){
this.handlers = {};
}
on(eventType, handler) { // 订阅事件
var self = this;
if (!(eventType in self.handlers)) {
self.handlers[eventType] = [];
}
self.handlers[eventType].push(handler);
return self ;
}
emit(eventType) { // 发布事件
var self = this;
var handlerArgs = Array.prototype.slice.call(arguments, 1);
var length = self.handlers[eventType].length
for (var i = 0; i < length; i++) {
self.handlers[eventType][i].apply(self, handlerArgs);
}
return self;
}
off(eventType, handler) { // 删除订阅事件
var currentEvent = this.handlers[eventType];
var len = 0;
if (currentEvent) {
len = currentEvent.length;
for (var i = len - 1; i >= 0; i--) {
if (currentEvent[i] === handler) {
currentEvent.splice(i, 1);
}
}
}
return self ;
}
}
//订阅者
function Observer1(data) {
console.log('订阅者1订阅了:' + data)
}
function Observer2(data) {
console.log('订阅者2订阅了:' + data)
}
var publisher = new Public();
//订阅事件
publisher.on('a', Observer1);
publisher.on('b', Observer1);
publisher.on('a', Observer2);
//发布事件
publisher.emit('a', '第一次发布的a事件');
publisher.emit('b', '第一次发布的b事件');
publisher.emit('a', '第二次发布的a事件');
//订阅者1订阅了:第一次发布a事件
//订阅者2订阅了:第一次发布a事件
//订阅者1订阅了:第一次发布b事件
//订阅者1订阅了:第二次发布a事件
//订阅者2订阅了:第二次发布a事件
可以看出来,订阅/发布模式下:订阅和发布是不直接调度的,而是通过调度中心来完成的,订阅者和发布者是互相不知道对方的,完全不存在耦合。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。