面試官:說說你對(duì)發(fā)布訂閱、觀察者模式的理解?區(qū)別?
文末本文轉(zhuǎn)載自微信公眾號(hào)「JS每日一題」,作者灰灰 。轉(zhuǎn)載本文請(qǐng)聯(lián)系JS每日一題公眾號(hào)。
一、觀察者模式
觀察者模式定義了對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都將得到通知,并自動(dòng)更新
觀察者模式屬于行為型模式,行為型模式關(guān)注的是對(duì)象之間的通訊,觀察者模式就是觀察者和被觀察者之間的通訊
例如生活中,我們可以用報(bào)紙期刊的訂閱來形象的說明,當(dāng)你訂閱了一份報(bào)紙,每天都會(huì)有一份最新的報(bào)紙送到你手上,有多少人訂閱報(bào)紙,報(bào)社就會(huì)發(fā)多少份報(bào)紙
報(bào)社和訂報(bào)紙的客戶就形成了一對(duì)多的依賴關(guān)系
實(shí)現(xiàn)代碼如下:
被觀察者模式
- 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));
- }
- }
觀察者:
- class Observer {
- constructor(name, subject) {
- this.name = name;
- if (subject) {
- subject.addObserver(this);
- }
- }
- notified(message) {
- console.log(this.name, 'got message', message);
- }
- }
使用代碼如下:
- 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');
上述代碼中,觀察者主動(dòng)申請(qǐng)加入被觀察者的列表,被觀察者主動(dòng)將觀察者加入列表
二、發(fā)布訂閱模式
發(fā)布-訂閱是一種消息范式,消息的發(fā)送者(稱為發(fā)布者)不會(huì)將消息直接發(fā)送給特定的接收者(稱為訂閱者)。而是將發(fā)布的消息分為不同的類別,無需了解哪些訂閱者(如果有的話)可能存在
同樣的,訂閱者可以表達(dá)對(duì)一個(gè)或多個(gè)類別的興趣,只接收感興趣的消息,無需了解哪些發(fā)布者存在
實(shí)現(xiàn)代碼如下:
- class PubSub {
- constructor() {
- this.messages = {};
- this.listeners = {};
- }
- // 添加發(fā)布者
- 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]));
- }
- }
發(fā)布者代碼如下:
- class Publisher {
- constructor(name, context) {
- this.name = name;
- this.context = context;
- }
- publish(type, content) {
- this.context.publish(type, content);
- }
- }
訂閱者代碼如下:
- class Subscriber {
- constructor(name, context) {
- this.name = name;
- this.context = context;
- }
- subscribe(type, cb) {
- this.context.subscribe(type, cb);
- }
- }
使用代碼如下:
- 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);
上述代碼,發(fā)布者和訂閱者需要通過發(fā)布訂閱中心進(jìn)行關(guān)聯(lián),發(fā)布者的發(fā)布動(dòng)作和訂閱者的訂閱動(dòng)作相互獨(dú)立,無需關(guān)注對(duì)方,消息派發(fā)由發(fā)布訂閱中心負(fù)責(zé)
三、區(qū)別
兩種設(shè)計(jì)模式思路是一樣的,舉個(gè)生活例子:
觀察者模式:某公司給自己?jiǎn)T工發(fā)月餅發(fā)粽子,是由公司的行政部門發(fā)送的,這件事不適合交給第三方,原因是“公司”和“員工”是一個(gè)整體
發(fā)布-訂閱模式:某公司要給其他人發(fā)各種快遞,因?yàn)?ldquo;公司”和“其他人”是獨(dú)立的,其唯一的橋梁是“快遞”,所以這件事適合交給第三方快遞公司解決
上述過程中,如果公司自己去管理快遞的配送,那公司就會(huì)變成一個(gè)快遞公司,業(yè)務(wù)繁雜難以管理,影響公司自身的主營(yíng)業(yè)務(wù),因此使用何種模式需要考慮什么情況兩者是需要耦合的
兩者區(qū)別如下圖:
在觀察者模式中,觀察者是知道Subject的,Subject一直保持對(duì)觀察者進(jìn)行記錄。然而,在發(fā)布訂閱模式中,發(fā)布者和訂閱者不知道對(duì)方的存在。它們只有通過消息代理進(jìn)行通信。
在發(fā)布訂閱模式中,組件是松散耦合的,正好和觀察者模式相反。
觀察者模式大多數(shù)時(shí)候是同步的,比如當(dāng)事件觸發(fā),Subject就會(huì)去調(diào)用觀察者的方法。而發(fā)布-訂閱模式大多數(shù)時(shí)候是異步的(使用消息隊(duì)列)
參考文獻(xiàn)
https://zh.wikipedia.org/zh-hans/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F
https://zh.wikipedia.org/wiki/%E5%8F%91%E5%B8%83/%E8%AE%A2%E9%98%85
https://www.cnblogs.com/onepixel/p/10806891.html
https://juejin.cn/post/6978728619782701087