定义区别
首先,观察者是经典软件设计模式
中的一种,但发布订阅只是软件架构中的一种消息范式
。所以不要再被"观察者模式和发布订阅模式xxx"这样的问题误导。
发布------订阅模式
发布订阅模式(Publish-Subscribe Pattern)是一种软件架构设计 模式。这属于行为型设计模式,用来解耦生产者(发布者)和消费者(订阅者)之间的关系。在这种模式中,发布者负责发布信息,订阅者则可以选择订阅他们感兴趣的消息类型。当有新信息发布时,订阅者就会收到通知并执行相应操作。
发布订阅的核心是基于一个中心来建立整个体系。其中发布者和订阅者是不会直接进行通信的,
重要概念
- 发布者(Publisher): 也被称为消息生产者或发送者,负责生成和发送消息。发布者也不知道这些消息会被谁接收。
- 订阅者(Subscriber): 也被称为信息消费者或接受者,等待并响应来自发布者的信息。订阅者只接受他们感兴趣的信息。
- 频道/主题(Topic): 这时信息的分类方式,发布者会将信息发送到特定的主题,订阅者则会订阅自己感兴趣的主题。
- 信息代理(Message Broker): 在一些实现中,为了更好管理发布者和订阅者之间的交互,通常会设置一个中间件------让消息代理来处理信息的路由。确保信息从发布者正确地流向感兴趣的订阅者。
这张图能更好的帮助你理解发布订阅模式。
优缺点
优点
- 松耦合: 主题和观察者之间的联系很弱,主题只知道观察者实现了某个接口,而并不关心他们是如何实现的。这使得我们可以独立复用主题或观察者中的任何一个。
- 扩展性好: 新的观察者可以在不修改主题的前提下添加到系统中,提高了系统的可扩展性。
- 促进广播通信: 一次的状态变化可以传递给多个观察者,支持广播式的通信机制。
缺点
- 导致内存泄漏: 如果观察者没有正确地从主题中注销自己,可能导致主题持有不再需要的观察者引用,从而阻止垃圾回收。
- 难以追踪: 如果观察者和被观察者之间的间接交互,调节和理解程序的行为可能变得更加复杂。
- 潜在性能问题: 如果有大量的观察者,通知过程可能变得低效,尤其是需要同步通知的情况。
实现代码
js
class PubSub {
constructor() {
this.messages = {};
this.listeners = {};
}
// 添加发布者
publish(type, content) {
const existContent = this.messages[type];
if (!existContent) {
this.messages[type] = [];
}
this.messages[type].push(content);
}
// 添加订阅者
subscribe(type, cb) {
const existListener = this.listeners[type];
if (!existListener) {
this.listeners[type] = [];
}
this.listeners[type].push(cb);
}
// 通知
notify(type) {
const messages = this.messages[type];
const subscribers = this.listeners[type] || [];
subscribers.forEach((cb, index) => cb(messages[index]));
}
}
发布者代码:
js
class Publisher {
constructor(name, context) {
this.name = name;
this.context = context;
}
publish(type, content) {
this.context.publish(type, content);
}
}
订阅者代码:
js
class Subscriber {
constructor(name, context) {
this.name = name;
this.context = context;
}
subscribe(type, cb) {
this.context.subscribe(type, cb);
}
}
使用代码:
js
const TYPE_A = 'music';
const TYPE_B = 'movie';
const TYPE_C = 'novel';
const pubsub = new PubSub();
const publisherA = new Publisher('publisherA', pubsub);
publisherA.publish(TYPE_A, 'we are young');
publisherA.publish(TYPE_B, 'the silicon valley');
const publisherB = new Publisher('publisherB', pubsub);
publisherB.publish(TYPE_A, 'stronger');
const publisherC = new Publisher('publisherC', pubsub);
publisherC.publish(TYPE_C, 'a brief history of time');
const subscriberA = new Subscriber('subscriberA', pubsub);
subscriberA.subscribe(TYPE_A, res => {
console.log('subscriberA received', res)
});
const subscriberB = new Subscriber('subscriberB', pubsub);
subscriberB.subscribe(TYPE_C, res => {
console.log('subscriberB received', res)
});
const subscriberC = new Subscriber('subscriberC', pubsub);
subscriberC.subscribe(TYPE_B, res => {
console.log('subscriberC received', res)
});
pubsub.notify(TYPE_A);
pubsub.notify(TYPE_B);
pubsub.notify(TYPE_C);
观察者模式
观察者模式(Observer Pattern) 是一种行为设计模式,定义了对象之间的一对多的依赖关系,使得一个对象("主题"或"被观察者")状态的变化可以自动通知其依赖的对象("观察者")。
这个模式至少有一个"被观察对象",可以有多个观察者来观察这个对象。他们之间的关系是由被观察者 主动建立的,而被观察者至少要有三种方法------添加观察者、移除观察者、通知观察者。
当被观察者将某个观察者添加到自己的观察者列表 后,观察者和被观察者的联系就建立起来了。之后只要被观察者在某一时刻触发通知观察者这个方法,观察者就可以收到来自观察者的信息。
重要概念
- 主题/被观察者(Subject):这是一个接口或抽象类,定义了添加、删除和通知观察者的功能。具体主题实现了这些方法,并在状态变化时通知所有已注册的观察者。
- 观察者(Observer): 这是另一个接口或抽象类,定义了一个更新的方法,这个方法是由主题来通知观察者发生了状态变化。具体观察者实现了这个接口以及如何响应主题的通知。
- 具体主题(ConcreteSubject):实现了Subject接口,包含一些能触发状态变化的方法,并在状态变化时通知观察者。
- 具体观察者(ConcreteObserver):实现了Observer接口,保持对主题状态的引用,并根据主题的状态更新自己。
优缺点
优点
- 松耦合: 主体和观察者之间相互独立,它们可以在互不影响的情况下开发、测试、复用和扩展。
- 可扩展性: 新类型的观察者会很容易添加进来,不需要修改主题代码。
- 促进广播通信: 一次状态变化可以传递给多个观察者,简化了信息的传播。
缺点
- 潜在性能问题: 如果有很多观察者,通知过程会变得很低效,特别是同步通知的情况下。
- 内存泄漏: 如果没有正确地从主题中注销观察者,可能导致内存泄漏,因为主题可能存在不再需要的观察者的引用。
- 复杂度增加: 随着观察者数量的增加,理解系统的行为变得困难。
实现代码
被观察者:
js
class Subject {
constructor() {
this.observerList = [];
}
addObserver(observer) {
this.observerList.push(observer);
}
removeObserver(observer) {
const index = this.observerList.findIndex(o => o.name === observer.name);
this.observerList.splice(index, 1);
}
notifyObservers(message) {
const observers = this.observeList;
observers.forEach(observer => observer.notified(message));
}
}
观察者:
js
class Observer {
constructor(name, subject) {
this.name = name;
if (subject) {
subject.addObserver(this);
}
}
notified(message) {
console.log(this.name, 'got message', message);
}
}
使用代码:
js
const subject = new Subject();
const observerA = new Observer('observerA', subject);
const observerB = new Observer('observerB');
subject.addObserver(observerB);
subject.notifyObservers('Hello from subject');
subject.removeObserver(observerA);
subject.notifyObservers('Hello again');
二者的区别
1. 信息传递机制:
- 观察者模式: 通常是依赖直接引用或注册机制来连接主题和观察者。当主题发生变化时,会直接调用所有已注册观察者的更新方法。
- 发布订阅模式: 引入一个中间件(信息代理)来管理主题和订阅者之间的交互。发布者发布信息到某个主题,然后由信息代理将这些信息转发给所有订阅了该主题的订阅者。这使得发布者和订阅者完全解耦,二者不需要知道对方的存在。 2. 耦合程度:
- 观察者模式: 虽然实现了耦合,但还是需要观察者向主题注册,而且主题需要维护一个观察者列表并通知他们。
- 发布订阅模式: 利用消息代理进一步减少了组件间的耦合度,发布者只需要发布,订阅者只关心接收的信息。 3. 灵活性和可扩展性:
- 观察者模式: 因为直接的依赖关系,所以在处理大规模系统或动态添加移除观察者比不上发布订阅模式。
- 发布订阅模式: 因为由信息代理来间接处理信息,所以支持更加复杂的信息路由策略、过滤和异步通信等。 4. 复杂度:
- 观察者模式: 相对简单,适用于简单场景。
- 发布订阅模式: 相对复杂,需要考虑额外的信息基础设施,而且还特别要注意错误处理、可靠性和性能优化等。
我们再来看个例子来区分这两种模式。
观察者模式:
想象一下你订阅了一个杂志。在这个场景中,杂志出版商(相当于"被观察者"或"主题")直接管理着一个订阅者的列表,也就是那些已经订阅了该杂志的读者(相当于"观察者")。每当有新一期杂志出版时,出版商会直接将新杂志寄送给所有订阅者。
发布订阅模式:
现在考虑一种不同的情况:社交媒体平台上的信息流更新。假设你在微博上关注了一些博主。当你关注某个博主时,实际上是告诉微博平台(消息代理)你想接收来自这些博主的新动态。当博主发布新内容时,他们只是简单地将这条新信息发布到微博平台上,而不需要知道谁会看到这条消息。微博平台则根据用户的关注列表,自动将这条新动态推送给所有关注该博主的用户。
通过这两个例子想必能帮你更好理解这两种模式。