观察者模式
建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。
角色:
-
被观察的对象:它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;
-
观察者对象:对观察数据的改变做出反应;
应用场景:
观察者模式在软件开发中应用非常广泛,如某电子商务网站可以在执行发送操作后给用户多个发送商品打折信息,某团队战斗游戏中某队友牺牲将给所有成员提示等等,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。
实现(nodejs):
/** * 通过node事件触发器 实现观察者模式 */ const EventEmitter = require('events') class Subject { constructor(eventName = 'someEvent', state = {}) { this.emitter = new EventEmitter(); this._state = state this._eventName = eventName; } //设置state 触发所有监听_eventName事件的handler setState(newState) { this._state = newState; this.emitter.emit(this._eventName, this._state); } //将事件的handel 绑定到观察者的update方法上 subscripe(fn) { return this.emitter.on(this._eventName, fn); } //将观察者的update方法从事件上解绑 unSubscripe(fn) { return this.emitter.removeListener(this._eventName, fn) } } //观察者 class Observer { constructor(subject) { this.subject = subject; this._fn = () => { }; } //传入具体的响应handler consume(fn) { this.noMoreConsume() this._fn = fn; this.subject.subscripe(fn); } //不在响应state的变化 noMoreConsume() { this.subject.unSubscripe(this._fn); } } function main() { const subjectOne = new Subject('learnObserverOne'); const observerOne = new Observer(subjectOne); observerOne.consume(state => { console.log(`observerOne has get ${state} when state change`); }) const observerTwo = new Observer(subjectOne); observerTwo.consume(state => { console.log(`observerTwo has get ${state} when state change`); }) //被观察者更改state 引起两个观察者的响应 subjectOne.setState('ya') //观察者One不在响应数据变化 observerOne.noMoreConsume() ////被观察者继续更改state 只有Two响应 subjectOne.setState('nan') //观察者Two不再响应数据 observerTwo.noMoreConsume() //没有观察者响应了 subjectOne.setState('end') } main();
运行结果:
优点:
-
观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
-
观察者模式在观察目标和观察者之间建立一个抽象的耦合。
-
观察者模式支持广播通信。
-
观察者模式符合“开闭原则”的要求。
缺点:
-
如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
-
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
-
观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
发布订阅模式
在发布-订阅模式中,消息的发送方,叫做发布者(publishers),消息的消费方叫做订阅者,但是消息不会直接发送给订阅者, 而是发送到一个第三方,它关联发布者与订阅者,存贮消息的状态(已消费,未消费)。
角色:
- 发布者: 负责生产消息
- 订阅者: 负责消费消息
- 消息队列:接受订阅者的发布,推送消息到订阅者,维护消息的状态
应用场景:
微服务之间的消息推送,一个服务产生的数据通过消息队列被多个服务使用
实现:
/** * 发布订阅模拟 */ const EventEmitter = require('events') /** * 发布 * todos 向某个消息队列发送消息 以供订阅者消费 */ class Publisher { //模拟连接一个mq服务 constructor(mq) { this.mq = mq; } /* 发布消息 */ produce(message) { console.log(`publish ${message.toString()}`); return this.mq.receiveMessage(message); } } /* * 订阅 * todos 1、订阅某个主题 2 当主题有相关的消息时能够消费消息,并给予反馈 */ class Subscriper { //模拟连接一个mq服务 constructor(mq) { this.mq = mq; } /* 消费消息 */ consume(fn) { return this.mq.bindConsumeFn(fn) } } /* 消息队列 */ class MessageQueue { constructor(subject) { //主题 this.subject = subject //消息队列 this.messages = []; //事件模型 this.emitter = new EventEmitter(); //启动通知 this.notify(); } /* 接受消息 */ receiveMessage(message) { this.messages.push(message); } /* 绑定订阅者的回调 */ bindConsumeFn(fn) { this.emitter.on(this.subject, fn); } /* 将消息发送到订阅者的回调 */ notify() { setInterval(_ => { console.log(`${this.messages.length} message didn't be consume!`); if (this.messages.length) { for(let message of this.messages) { this.emitter.emit(this.subject, message); } } }, 3000) } /* 删除已消费的消息 */ ack(message) { let index = this.messages.findIndex(item => item === message); this.messages.splice(index, 1); console.log(`ack be called remove ${message.toString()} from messages!`); } } //一个test主题的mq队列 const mq = new MessageQueue('test'); //生产者连接mq const publisher = new Publisher(mq); //消费者连接mq const subscripter = new Subscriper(mq); //每秒生产一条消息 setInterval(_ => { //使用Symbol生成唯一message let message = Symbol(`${Math.ceil(Math.random(1) * 100)}`); publisher.produce(message); }, 1000) subscripter.consume(message => { console.log(`recevie ${message.toString()} from mq!`); //确认消费已经消费 subscripter.mq.ack(message) })
运行结果:
总结
从表面上看:
- 观察者模式里,只有两个角色 —— 观察者 + 被观察者
- 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 ——第三方消息维护者
往更深层次讲:
- 观察者和被观察者,是松耦合的关系
- 发布者和订阅者,则完全不存在耦合
从使用层面上讲:
- 观察者模式,多用于单个应用内部
- 发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件