## 觀察者模式
> 觀察者模式定義了對象之間的一對多依賴,這 樣一來,當一個對象改變狀態時,它的所有依賴者都 會收到通知并自動更新。

* 1. 報社的業務就是出版報紙。
* 2. 向某家報社訂閱報紙,只要他們有新報紙出版,就會給你送 來。只要你是他們的訂戶,你就會一直收到新報紙。
* 3. 當你不想再看報紙的時候,取消訂閱,他們就不會再送新報 紙來。
* 4. 只要報社還在運營,就會一直有人(或單位)向他們訂閱報 紙或取消訂閱報紙。

觀察者模式提供了一種對象設計,讓主題和觀察者之間松耦合。
關于觀察者的一切,主題只知道觀察者實現了某個接口(也就是Observer接口)。主 題不需要知道觀察者的具體類是誰、做了些什么或其他任何細節。
任何時候我們都可以增加新的觀察者。因為主題唯一依賴的東西是一個實現 Observer接口的對象列表,所以我們可以隨時增加觀察者。事實上,在運行時我們可 以用新的觀察者取代現有的觀察者,主題不會受到任何影響。同樣的,也可以在任何 時候刪除某些觀察者。
有新類型的觀察者出現時,主題的代碼不需要修改。假如我們有個新的具體類需要當 觀察者,我們不需要為了兼容新類型而修改主題的代碼,所有要做的就是在新的類里 實現此觀察者接口,然后注冊為觀察者即可。主題不在乎別的,它只會發送通知給所 有實現了觀察者接口的對象。
改變主題或觀察者其中一方,并不會影響另一方。因為兩者是松耦合的,所以只要他 們之間的接口仍被遵守,我們就可以自由地改變他們。
基本概念介紹
觀察者(observer) 模式廣泛用于客戶端Javascript[編程](http://www.2cto.com/kf)中。所有的[瀏覽器](http://www.2cto.com/os/liulanqi/)事件都是該模式的例子。它的另一個名字也稱為自定義事件(custom events),與那些由瀏覽器觸發的事件相比,自定義事件表示是由你編程實現的事件。此外,該模式的另一個別名也稱為訂閱/發布(subscriber/publisher)模式。
設計該模式背后的主要動力是促進形成松散耦合。在這種模式中,并不是一個對象調用另一個對象的方法,而是一個對象訂閱另一個對象的特定活動并在狀態改變后獲得通知。訂閱者也稱為觀察者,而補觀察的對象稱為發布者或主題。當發生了一個重要的事件時,發布者將會通知(調用)所有訂閱者并且可能經常以事件對象的形式傳遞消息。
示例:雜志訂閱
假設有一個發布者paper,它每天出版報紙及月刊雜志。訂閱者joe將被通知任何時候所發生的新聞。
該paper對象需要一個subscribers(topics?)屬性,該屬性是一個存儲所有訂閱者的數組。訂閱行為只是將其加入到這個數組中。當一個事件發生時,paper將會循環遍歷訂閱者列表并通知它們。通知意味著調用訂閱者對象的某個方法。故當用戶訂閱信息時,該訂閱者需要向paper的subscribe()提供它的其中一個方法。
paper也提供了unsubscribe()方法,該方法表示從訂閱者數組(即subscribers屬性)中刪除訂閱者。paper最后一個重要的方法是publish(),它會調用這些訂閱者的方法,總而言之,發布者對象paper需要具有以下這些成員:
1.subscribers 一個數組 2.subscribe() 將訂閱者添加到subscribers數組中 3.unsubscribe() 從subscribers數組中刪除訂閱者 4.publish() 循環遍歷subscribers數組中的每一個元素,并且調用他們注冊時所提供的方法
所有這三種方法都需要一個topic參數,因為發布者可能觸發多個事件(比如同時發布一本雜志和一份報紙)而用戶可能僅選擇訂閱其中一種,而不是另外一種。
由于這些成員對于任何發布者對象都是通用的,故將它們作為獨立對象的一個部分來實現是很有意義的。那樣我們可將其復制到任何對象中,并將任意給定對象變成一個發布者。
JS里對觀察者模式的實現是通過回調來實現的,我們來先定義一個pubsub對象,其內部包含了3個方法:訂閱、退訂、發布。
~~~
var pubsub = {};
(function (q) {
var topics = {}, // 回調函數存放的數組
subUid = -1;
// 發布方法
q.publish = function (topic, args) {
if (!topics[topic]) {
return false;
}
setTimeout(function () {
var subscribers = topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func(topic, args);
}
}, 0);
return true;
};
//訂閱方法
q.subscribe = function (topic, func) {
if (!topics[topic]) {
topics[topic] = [];
}
var token = (++subUid).toString();
topics[topic].push({
token: token,
func: func
});
return token;
};
//退訂方法
q.unsubscribe = function (token) {
for (var m in topics) {
if (topics[m]) {
for (var i = 0, j = topics[m].length; i < j; i++) {
if (topics[m][i].token === token) {
topics[m].splice(i, 1);
return token;
}
}
}
}
return false;
};
} (pubsub));
~~~
使用方式如下:
~~~
//來,訂閱一個
pubsub.subscribe('example1', function (topics, data) {
console.log(topics + ": " + data);
});
//發布通知
pubsub.publish('example1', 'hello world!');
pubsub.publish('example1', ['test', 'a', 'b', 'c']);
pubsub.publish('example1', [{ 'color': 'blue' }, { 'text': 'hello'}]);
~~~
試試多個訂閱者訂閱同個主題:
~~~
//來,訂閱一個
pubsub.subscribe('example1', function (topics, data) {
console.log(topics + ": " + data);
});
//來,再訂閱一個
pubsub.subscribe('example1', function (topics, data) {
console.log(topics + "******* " + data);
});
//發布通知
pubsub.publish('example1', 'hello world!');
~~~