對發佈-訂閱者模式的解析

介紹

在使用發佈-訂閱者模式以前,先了解什麼是發佈-訂閱者模式。發佈訂閱者模式是一種一對多的依賴關係。多個對象(訂閱者:subscriber)同時監聽同一對象(發佈者:publisher)的數據狀態變化。當發佈者的數據狀態發生變化的時候,就會通知全部的訂閱者,同時還可能以事件對象的形式傳遞一些消息。在生活中就有不少這樣的例子,好比微信公衆號的發佈文章與用戶的訂閱公衆號,報紙的發刊以及每家每戶的訂閱報紙等等,這些都是對發佈訂閱者模式很好的應用。數組

這樣,咱們就能知曉發佈者以及訂閱者包含的內容了,歸納以下:bash

發佈者(publisher):

  • 一個對象 subscribers:{type:[subscriberFn,...]} 包了含訂閱的類型及其相關訂閱者(因爲訂閱的事件可能多種因此此處需添加一個訂閱類型)
  • 一個函數 subscribe(newSubscriberFn,type) 添加新的訂閱者到對應訂閱類型的訂閱者數組中
  • 一個函數 unsubscribe(subscriberFn,type) 從數組中移除指定訂閱者
  • 一個函數 publish(type) 當發佈者的某一狀態改變是,通知全部的訂閱者,即遍歷訂閱者提供的方法

訂閱者(subscriber):

  • 一個函數 subscriberFn() 當發佈者狀態發生改變時通知所使用的的回調函數

發佈訂閱的整個過程就以下所示:微信

理論的東西弄明白了,下面咱們就進行具體的應用。異步

栗子

場景:某報社發佈的報紙分爲日報和月報。小王訂閱了這家報社的日報,小李訂閱了這家報紙的月報,小趙兩類的報紙都訂閱了。當報社有新的報紙出版了,小王、小李以及小趙都能閱讀到他們訂閱的報紙。函數

首先定義報社這個對象:ui

const PaperPublisher = {
	subscribers: {
		dailyPaper: [],
		monthlyPaper: [],
		all: []
	},
	subscribe (newSubscriberFn, type) {
		let type = type ? type : 'all';
		this.subscribers[type].push(newSubscriberFn);
	},
	unsubscribe (subscriberFn, type) {
		let type = type ? type : 'all',
			index = 0;
		for (let i = 0, len = this.subscribers[type].length; i < len; i++) {
			if (this.subscribers[type][i] == subscriberFn) {
				index = i;
				break;
			}
		}
		this.subscribers[type].splice(index,1);
	},
	publish (type,value) {
		for (let i = 0, len = this.subscribers[type].length; i < len; i++) {
			this.subscribers[type][i](value);
		}
		for (let i = 0, len = this.subscribers.all.length; i < len; i++) {
			this.subscribers.all[i](value);
		}
	},
	publishDailyPaper () {  // 發佈日報
		this.publish('dailyPaper','日報的內容');
	},
	publishMonthlyPaper () {  // 發佈月報
		this.publish('monthlyPaper','月報的內容');
	}
}
複製代碼

而後分別定義小王、小李、小趙this

// 小王
const XiaoWangSubscriber = {
	subscriberFn (value) {
		console.log(`我是小王,正在讀 ${value}`);
	}
}
// 小李
const XiaoLiSubscriber = {
	subscriberFn (value) {
        console.log(`我是小李,正在讀 ${value}`);
	}
}
// 小趙
const XiaoZhaoSubscriber = {
	subscriberFn (value) {
		console.log(`我是小趙,正在讀 ${value}`);
	}
}
複製代碼

接下來,讓小王訂閱日報,小李訂閱月報,小趙兩類的報紙都訂閱spa

PaperPublisher.subscrib(XiaoWangSubscriber.subscriberFn,'dailyPaper');
PaperPublisher.subscrib(XiaoLiSubscriber.subscriberFn,'monthlyPaper');
PaperPublisher.subscrib(XiaoZhaoSubscriber.subscriberFn);
複製代碼

當報社發佈日報,小王和小趙能收到報紙並閱讀code

PaperPublisher.publishDailyPaper();
 // 我是小王,正在讀 日報的內容
 // 我是小趙,正在讀 日報的內容
複製代碼

當報社發佈月報,小李和小趙能收到報紙並閱讀cdn

PaperPublisher.publishMonthlyPaper();
 // 我是小李,正在讀 月報的內容
 // 我是小趙,正在讀 月報的內容
複製代碼

小王由於工做緣由,去外地出差了,因而取消了日報的訂閱

PaperPublisher.unsubscribe(XiaoWangSubscriber.subscriberFn,'dailyPaper');
複製代碼

以上就是整個栗子的所有代碼了。

進一步提高

其實全部的發佈者,前四點屬性和方法都是必備的,因此咱們可以提取出這些屬性和方法存放在一個公用的對象中。在以後的使用中,咱們就能夠將它們複製到任何一個對象中,將這些對象轉換爲訂閱者。特別的在函數unsubscribe和publish中都存在遍歷subscribers中的數據的操做,因此在公用的對象中,定義了一個visitSubscribers()方法。下面就是對公用對象的定義:

var publisher = {
    subscribers: {
        all: []
    },
    subscribe: function (fn, type) {
        let type = type || 'all';
        if (typeof this.subscribers[type] === "undefined") {
            this.subscribers[type] = [];
        }
        this.subscribers[type].push(fn);
    },
    unsubscribe: function (fn, type) {
        this.visitSubscribers('unsubscribe', fn, type);
    },
    publish: function (type, publication) {
        this.visitSubscribers('publish', publication, type);
    },
    visitSubscribers: function (action, arg, type) {
        let pubtype = type || 'all',
            subscribers = this.subscribers[pubtype],
            i,
            max = subscribers.length;

        for (i = 0; i < max; i += 1) {
            if (action === 'publish') {
                subscribers[i](arg);
            } else {
                if (subscribers[i] === arg) {
                    subscribers.splice(i, 1);
                }
            }
        }
    }
};
複製代碼

除了公用的對象接着還須要定義一個函數來將一個普通的對象包裝成一個發佈者:

function makePublisher(o) {
    var i;
    for (i in publisher) {
        if (publisher.hasOwnProperty(i) && typeof publisher[i] === "function") {
            o[i] = publisher[i];
        }
    }
    o.subscribers = {any: []};
}
複製代碼

接下來,咱們就能夠以另一種方式定義PaperPublisher對象了:

// PaperPublisher對象自己包括髮布日報和發佈週報的兩個方法
var PaperPublisher = {
    publishDailyPaper () {  // 發佈日報
		this.publish('dailyPaper','日報的內容');
	},
	publishMonthlyPaper () {  // 發佈月報
		this.publish('monthlyPaper','月報的內容');
	}
};
// 將對象包裝成發佈者
makePublisher(PaperPublisher);
複製代碼

發佈-訂閱者模式的優缺點

優勢:

  • 實現時間上的解耦(組件,模塊之間的異步通信)
  • 對象之間的解耦,交由發佈訂閱的對象管理對象之間的耦合關係.

缺點:

  • 建立訂閱者自己會消耗內存,訂閱消息後,也許,永遠也不會有發佈,而訂閱者始終存在內存中.
  • 對象之間解耦的同時,他們的關係也會被深埋在代碼背後,這會形成必定的維護成本.
相關文章
相關標籤/搜索