定義
觀察者模式定義了物件之間一對對多的依賴關係,當一個物件改變了狀態,它的所有依賴會被通知,然後自動更新。
和其他模式相比,這種模式又增加了一個原則:
- 在相互作用的物件之間進行鬆散耦合設計
所以主要的想法是我們要有一個大的物件來處理訂閱(Subject/Observable),以及很多物件(Observers)被訂閱然後等待事件觸發。
還有一個重要的點就是Observers接受訊息的順序是隨機的,所以你不要依照Observers新增的順序。
基礎例子
var Observable = {
observers: []
, addObserver: function(observer) {
this.observers.push(observer)
}
, removeObserver: function(observer) {
var index = this.observers.indexOf(observer)
if (~index) {
this.observers.splice(index, 1)
}
}
, notifyObservers: function(message) {
for (var i = this.observers.length - 1; i >= 0; i--) {
this.observers[i](message)
};
}
}
Observable.addObserver(function(message){
console.log("First observer message:" + message)
})
var observer = function(message){
console.log("Second observer message:" + message)
}
Observable.addObserver(observer)
Observable.notifyObservers('test 1')
// Second observer message:test 1
// First observer message:test 1
Observable.removeObserver(observer)
Observable.notifyObservers('test 2')
// First observer message:test 2複製程式碼
如果你想用某種形式的ID來刪除,而不是傳入回撥函式,那麼程式碼需要改成下面這樣:
var Observable = {
observers: []
, lastId: -1
, addObserver: function(observer) {
this.observers.push({
callback: observer
, id: ++this.lastId
})
return this.lastId
}
, removeObserver: function(id) {
for (var i = this.observers.length - 1; i >= 0; i--) {
this.observers[i]
if (this.observers[i].id == id) {
this.observers.splice(i, 1)
return true
}
}
return false
}
, notifyObservers: function(message) {
for (var i = this.observers.length - 1; i >= 0; i--) {
this.observers[i].callback(message)
};
}
}
var id_1 = Observable.addObserver(function(message){
console.log("First observer message:" + message)
})
var observer = function(message){
console.log("Second observer message:" + message)
}
var id_2 = Observable.addObserver(observer)
Observable.notifyObservers('test 1')
Observable.removeObserver(id_2)
Observable.notifyObservers('test 2')複製程式碼
pull vs push
觀察者模式有兩種重要的策略:
Push - 當一個事件發生後將通知所有觀察者,並把所有的新資料傳給他們
Pull - 當一個事件發生後將通知所有觀察者,每個觀察者將拉取自己需要的資料
當你只想要你需要的資料,Pull 方法更為可取,在下面這個例子中,Subject將會通知觀察者發生了改變,然後每個觀察者取它們自己需要的資料。並且在這個例子中,我們隱藏觀察者陣列,並且將各自的私有資料存到閉包中。
var Observable = {}
;(function(O){
var observers = []
, privateVar
O.addObserver = function(observer) {
observers.push(observer)
}
O.removeObserver = function(observer) {
var index = observers.indexOf(observer)
if (~index) {
observers.splice(index, 1)
}
}
O.notifyObservers = function() {
for (var i = observers.length - 1; i >= 0; i--) {
observers[i].update()
};
}
O.updatePrivate = function(newValue) {
privateVar = newValue
this.notifyObservers()
}
O.getPrivate = function() {
return privateVar
}
}(Observable))
Observable.addObserver({
update: function(){
this.process()
}
, process: function(){
var value = Observable.getPrivate()
console.log("Private value is: " + value)
}
})
Observable.updatePrivate('test 1')
// Private value is: test 1
Observable.updatePrivate('test 2')
// Private value is: test 2複製程式碼
觀察者模式之主題
為了不建立多個可觀察物件,最好在觀察者模式中加入主題功能,最簡單的形式看起來是這樣的:
var Observable = {
observers: []
, addObserver: function(topic, observer) {
this.observers[topic] || (this.observers[topic] = [])
this.observers[topic].push(observer)
}
, removeObserver: function(topic, observer) {
if (!this.observers[topic])
return;
var index = this.observers[topic].indexOf(observer)
if (~index) {
this.observers[topic].splice(index, 1)
}
}
, notifyObservers: function(topic, message) {
if (!this.observers[topic])
return;
for (var i = this.observers[topic].length - 1; i >= 0; i--) {
this.observers[topic][i](message)
};
}
}
Observable.addObserver('cart', function(message){
console.log("First observer message:" + message)
})
Observable.addObserver('notificatons', function(message){
console.log("Second observer message:" + message)
})
Observable.notifyObservers('cart', 'test 1')
// First observer message:test 1
Observable.notifyObservers('notificatons', 'test 2')
// Second observer message:test 2複製程式碼
更高階的功能會有如下特性:
子話題 (比如 /bar/green 或者 bar.green)
釋出到主題傳播到子主題
釋出到所有主題
訂閱者優先順序
觀察者模式之jQuery.Callback
jQuery有一個很好的特性,$.Callback。除了經典的觀察功能外,他還有其他的一組標記:
once: 確保callback列表只能被觸發一次
memory: 跟蹤資料
unique: 確保回撥函式只會被新增一次。
stopOnFalse: 當回撥函式返回false,則中斷呼叫。
使用這些選項你可以定製的你的觀察者。讓我們來看看最基礎的例子:
var callbacks = jQuery.Callbacks()
, Topic = {
publish: callbacks.fire,
subscribe: callbacks.add,
unsubscribe: callbacks.remove
}
function fn1( value ){
console.log( "fn1: " + value );
}
function fn2( value ){
console.log("fn2: " + value);
}
Topic.subscribe(fn1);
Topic.subscribe(fn2);
Topic.publish('hello world!');
Topic.publish('woo! mail!');複製程式碼
如果你想看一些複雜的例子,請點這裡。
CoffeeScript例子
下面是一個簡單的例子,幾乎相同的例子可以在“CoffeeScript Cookbook [7]”中找到。
class Observable
constructor: () ->
@subscribers = []
subscribe: (callback) ->
@subscribers.push callback
unsubscribe: (callback) ->
@subscribers = @subscribers.filter (item) -> item isnt callback
notify: () ->
subscriber() for subscriber in @subscribers
class Observer1
onUpdate: () ->
console.log "1st got new message"
class Observer2
onUpdate: () ->
console.log "2nd updated"
observable = new Observable()
observer1 = new Observer1()
observer2 = new Observer2()
observable.subscribe observer1.onUpdate
observable.subscribe observer2.onUpdate
observable.notify()複製程式碼
資料
(github) shichuan / javascript-patterns / design-patterns / observer.html and jQuery examples
(書) JavaScript Patterns: Build Better Applications with Coding and Design Patterns
(書) Learning JavaScript Design Patterns: A JavaScript and jQuery Developer's Guide
(書) Pro JavaScript Design Patterns: The Essentials of Object-Oriented JavaScript Programming
原文:bumbu.github.io/javascript-…
譯者:繆宇