一、概念
1、Observer模式的概念
(Observer)被称作发布-订阅者模式或消息机制,定义了一种依赖关系,解决了主体对象与观察者之间功能的耦合。
(Observer)模式是行为模式之一,它的作用是当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态。
好处:
- 可广泛应用于异步编程中,是一种替代传递回调函数的方案。
- 可取代对象之间硬编码的通知机制,一个对象不用再显示地调用另外一个对象的某个接口。 两对象轻松解耦。
2、Observer模式的角色
Subject(被观察者)
被观察的对象。当需要被观察的状态发生变化时,需要通知队列中所有观察者对象。Subject需要维持(添加,删除,通知)一个观察者对象的队列列表。
ConcreteSubject -- 被观察者的具体实现。包含一些基本的属性状态及其他操作。
Observer(观察者)
接口或抽象类。当Subject的状态发生变化时,Observer对像将通过一个callback函数得到通知。
ConcreteObserver -- 观察者的具体实现。得到通知后将完成一些具体的业务逻辑处理。
二、 应用场景
javascript
<script>
let subscribe = { 'eating': [] };
// 订阅
subscribe.eating[0] = () => {
console.log('我是Ben,我要去吃饭');
}
subscribe.eating[1] = () => {
console.log('我是James,我要去吃饭');
}
subscribe.eating[2] = () => {
console.log('我是Oscar,我要去吃饭');
}
// 发布
let treat = (() => {
let eatingPersion = subscribe.eating;
let len = eatingPersion.length;
for (let i = 0; i < len; i++) {
subscribe.eating[i]();
}
})();
/*
如果Judy也想去吃饭的话,不需要告诉任何人, 只需要自己订阅就好啦。
But这里有个缺陷,就是Ben今天想减肥,想要取消订阅,没有提供取消订阅的的机制
*/
// 完善先取消订阅模式
var Observer = (function () {
//静态私有变量,用来存放不对外暴露的消息列队
var _rank = {};
return {
//订阅消息接口
register: function (type, fn) {
//如果这个消息的方法不存在,则放入消息列队中
if (typeof _rank[type] === 'undefined') {
_rank[type] = [fn];
}
//如果这个消息的方法是存在的,就给这个消息的数组中添加这个方法
else {
_rank[type].push(fn);
}
},
//发布消息接口
fire: function (type, argsJson) {
//如果消息未定义,就返回
if (!_rank[type]) { return; }
//对消息的参数进行包装
var newArgs = {
type: type,
args: argsJson || {}
};
//遍历执行这个消息列队中的方法
for (var i = 0, l = _rank[type].length; i < l; i++) {
_rank[type][i].call(this, newArgs);
}
},
//消息注销方法
remove: function (type, fn) {
//确保这个消息列队是存在的
if (_rank[type] instanceof Array) {
//从最后一个方法开始查找
for (var i = _rank[type].length - 1; i >= 0; i--) {
//如果存在就从列队中删除
_rank[type][i].toString() === fn.toString() && _rank[type].splice(i, 1);
}
}
}
}
})();
// PM瑞瑞发布
Observer.fire('eating', { msg: 'pm瑞瑞请客~' });
// 订阅
Observer.register('eating', function (params) {
console.log(params.type + ': 我是Ben,我要去吃饭,' + params.args.msg);
});
Observer.register('eating', function (params) {
console.log(params.type + ': 我是James,我要去吃饭,' + params.args.msg);
});
Observer.register('eating', function (params) {
console.log(params.type + ': 我是Oscar,我要去吃饭,' + params.args.msg);
});
// Ben取消订阅
Observer.remove('eating', function (params) {
console.log(params.type + ': 我是Ben,我要去吃饭,' + params.args.msg);
});
// PM瑞瑞重新发布
Observer.fire('eating',{msg:'pm瑞瑞请客~'});
/*
此时,如果还有人要吃饭,只需要订阅一个位置就好,并且其它的活动也可以通过模式去订阅,
发布,撤销~。观察者的使用环境是这样的,例如:多人协作中在初始化函数init()中不同人
都要加事件,如果直接添加,总是怕出问题,此时,如果通过定义一个观察者,
就方便多了,可以放心大胆的操作。又例如年久失修的代码要更改,但是关系错中复杂,最好的
方式就是给事件都归入消息列队,然后添加自己的事件,这样就万事大吉了~
*/
</script>
javascript
/**
* 发布订阅模式(观察者模式)
* handles:
* on: 订阅事件
* emit: 发布事件
* off: 删除事件
**/
class PubSub {
constructor() {
this.handles = {} // 事件处理函数集合
}
//订阅事件
on(eventType, handle) {
if (!this.handles.hasOwnProperty(eventType)) {
this.handles[eventType] = []
}
if (typeof handle == 'function') {
this.handles[eventType].push()
} else {
throw new Error('缺少回调函数')
}
return this;
}
// 发布事件
emit(eventType, ...args) {
if (this.handles.hasOwnProperty(eventType)) {
this. Handles[eventType].forEach((item, key, arr) => {
item.apply(null, args)
})
} else {
throw new Error(`"${eventType}"事件未注册`)
}
return this;
}
// 删除事件
off(eventType, handle) {
if (!this.handles.hasOwnProperty(eventType)) {
throw new Error(`"${eventType}"事件未注册`)
} else if (typeof handle != 'function') {
throw new Error('缺少回调函数')
} else {
this.handles[eventType].forEach((item, key, arr) => {
if (item == handle) {
this.handles[eventType].splice(key, 1)
}
})
}
return this;
}
}
let pubsub = new PubSub();
function callback() {
console.log('you are so nice')
}
// 订阅:如下在事件eventTypeName上添加了两个回调
pubsub.on('eventTypeName', (...args) => {
console.log(args.join(' '))
}).on('eventTypeName', callback)
// 发布:
pubsub.emit('eventTypeName', 'whar', 'a', 'fucking day'); //发布 依次执行 eventTypeName 事件中的 所以方法
pubsub.off('eventTypeName', callback) // 删除只是删除 eventTypeName 事件的 callback 方法
pubsub.emit('eventTypeName', 'fucking', 'again')
/*
输出值:
what a fucking day
you are so nice
fucking again
*/
总结: 发布---订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦。它的应用非常广泛,既可以 用在异步编程中,也可以帮助我们完成更松耦合的代码编写。发布---订阅模式还可以用来帮助实现一些别 的设计模式,比如中介者模式。从架构上来看,无论是 MVC 还是 MVVM, 都少不了发布---订阅模式的参 与,而且JavaScript本身也是一门基于事件驱动的语言。 当然,发布---订阅模式也不是完全没有缺点(浪费内存)。创建订阅者本身要消耗一定的时间和内存, 而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外,发 布---订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被 深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者(b订阅a的消息并发布 给c)嵌套到一起的时候,要跟踪一个bug不是件轻松的事情。