观察者模式,和发布订阅模式,有什么区别?
你可能听过这二者是一样的,没有差别。但是二者其实是不一样的,属于两种不同模式。
观察者模式
所谓观察者模式,其实就是为了实现松耦合。
举例:以一个小宝宝为例,每当小宝宝的心情发生变化,changed() 方法被调用,然后我们在 changed() 方法里面,更新宝宝的心情。
假如我们要在小宝宝心情发生变化之后,增加新的操作,更新宝宝的其他特征,的这时候我们就需要去修改changed()
方法的代码,这就是紧耦合的坏处。
观察者模式里面, changed()
方法所在的实例对象,就是被观察者(小宝宝),它只需维护一套观察者(爸爸、妈妈、...其他关心小宝宝的)。这些(爸爸、妈妈、...其他关心小宝宝的)实现相同的接口(关系小宝宝的变化),宝宝只需要,通知爸爸妈妈去调用拿个统一的方法就好了。
JavaScript 实现观察者模式
// 被观察者:我家小宝宝,心情好不好
// 观察者: 爸爸、妈妈
// 心情好 -> 心情不好
class Subject {
constructor (name) {
this.name = name;
this.observers = [];
this.state = '心情很美丽';
}
// 收集观察者
attach (observer) {
this.observers.push(observer);
}
// 改变小宝宝心情
setState (newState) {
this.state = newState;
this.observers.forEach(o => o.update(newState));
}
}
class Observer {
constructor (name) {
this.name = name;
}
// 用来通知所有的观察者 小宝宝心情变化了
update (newState) {
console.log(this.name, newState);
}
}
let sub = new Subject('小宝宝');
let o1 = new Observer('爸爸');
let o2 = new Observer('妈妈');
sub.attach(o1);
sub.attach(o2);
sub.setState('心情不好');
Vue 中在实现双向绑定中也使用了观察者模式。请查看另一篇 实现简易版的 Vue 。
发布订阅模式
很让人误会的就是 发布 和 "小宝宝" 的工作是一样的,都是用来进行集体通知的。
这点很相同的。
但是不同的是,是否是直接通知的一个问题。"小宝宝" 是根据自己的变化去主动通知爸爸妈妈的,而发布 / 订阅不是直接产生关系的。它们是通过第三者去管理的。
举例:以买奶粉为例,通过网上客服订购,由客服来记录所有需要订购的用户,并且由客服来奶粉公司去购买奶粉。
也就是说,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。
JavaScript 实现发布订阅
function Events() {
this.topics = {};
}
Events.prototype.subscribe = function (topic, handler) {
if (!this.topics.hasOwnProperty(topic)) {
this.topics[topic] = [];
}
this.topics[topic].push(handler);
}
Events.prototype.publish = function (topic, message) {
if (this.topics.hasOwnProperty(topic)) {
this.topics[topic].forEach(function (handler) {
handler(message ? message: {});
})
}
}
Events.prototype.remove = function (topic, handler) {
if (!this.topics.hasOwnProperty(topic)) {
return;
}
const _that = this;
let handlerIndex = -1;
this.topics[topic].forEach(function (item, index) {
if (item === handler) {
handlerIndex = index;
_that.topics[topic].splice(index, 1);
}
})
}
Events.prototype.removeAll = function (topic) {
if (this.topics.hasOwnProperty(topic)) {
this.topics[topic].length = 0;
}
}
// test
const events = new Events();
function handler1(info) {
console.log('handler1', info);
}
events.subscribe('click', handler1);
events.publish('click', 'hello world');
events.remove('click', handler1);
实现 Node.JS 中的 event
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events.hasOwnProperty(event)) {
this.events[event] = [];
}
this.events[event].push(callback);
return this;
}
emit(event, ...args) {
if (this.events.hasOwnProperty(event)) {
this.events[event].forEach(callback => {
callback(...args);
})
}
return this;
}
off(event, callback) {
if (!this.events.hasOwnProperty(event)) {
return;
}
let callbacks = this.events[event]
this.events[event] = callbacks.filter(fn => fn !== callback);
return this;
}
once(event, callback) {
let wrapFun = (...args) => {
callback(...args);
this.off(event, wrapFun);
};
this.on(event, wrapFun);
return this;
}
}
const events = new EventEmitter();
function handler(info, info1) {
console.log(info, info1);
}
events.on('click', handler);
events.emit('click', 'hello world', 'hello bai');
events.off('click', handler);
events.once('hand', (a, b, c) => {
console.log(a, b, c);
})
events.emit('hand', '小白', '小黑', '小紫');