1. 程式人生 > >【設計模式】Observer(觀察者)模式----物件行為模式

【設計模式】Observer(觀察者)模式----物件行為模式

1,意圖

    定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知 並 被自動更新。

2,別名

    依賴(Dependents),釋出-訂閱模式(Publish-Subscribe)

3,動機

    將一個系統分割成一系列相互協作的類有一個常見的副作用,需要維護相關物件間的一致性。我們不希望為了維持一致性而使各個類緊密耦合,因為這樣降低了它們的可重用性。



    許多圖形使用者介面工具箱將使用者應用的介面表示與底下的應用資料分離。 定義資料的類和負責介面表示的類可以各自獨立的地服用。【題外話: 是否可以將我們現在流行的mvvm構建UI的框架和現在的這個例子類比 : 目標就是store裡面的資料,觀察者是什麼,是各個引用了此store裡面資料的元件; 目標是當前元件內state裡面的data,觀察者是什麼,是當前元件裡面綁定了該data的html元素。

】  當我們修改目標資料的時候,會通知各個觀察者:我們的資料發生了變化。如果觀察者也就是餅狀圖更新了目標subject的資料  也會通知他表格。



    Observer模式描述瞭如何建立這種關係。這一種模式中關鍵物件是目標(subject) 觀察者(observer)。 一個目標可以 有任意數目的依賴它的觀察者。一旦目標的狀態發生了改變,所有的觀察者都得到了通知。並且通知之後,觀察者應該對這個通知做出響應,有的是將觀察者當前自己的某個資料的狀態與目標某個資料狀態同步; 有的則是根據目標的通知做出響應的其他操作【mvvm 檢視的更新

】。

    這種互動也稱之為釋出-訂閱(publish-subscribe)。目標是通知的釋出者,它發出通知時並不需要知道誰是她的觀察者。可以有任意數量的觀察者訂閱並接受通知。

----------------------------------------------------------------------------------------------------------

我們用typescript來實現一下這個:

程式碼層級:



Observer觀察者和Subject目標兩個基類如下:

import Subject
from "./Subject";
export default class Observer { name: string //為啥需要name屬性, 目標subject移除觀察者需要這個引數,你也可以設定一個唯一不變的uuid值 update( sub: Subject) {} //更新操作之前,傳入Subject引數(可以不需要該引數),對其進行檢查,保證發出通知的目標是該observer的目標 }


import Observer from './Observer' export default class Subject { private observers: Observer[] protected constructor(){ this. observers = [] } public attach( obj: Observer) { this. observers. push( obj) } public dettach( obj: Observer) { let index = this. observers. findIndex( item => item. name === obj. name) this. observers. splice( index, 1) } public getObservers () { return this. observers } public notify() {} }

實現類:

import ConcreteSubject from './ConcreteSubject' import Observer from './Observer'
export default class ConcreteObserver extends Observer { subject: ConcreteSubject name: string //為啥需要name屬性, 目標subject移除觀察者需要這個引數,你也可以設定一個唯一不變的uuid值 constructor( object: { sub: ConcreteSubject, name: string}) { super() this. subject = object. sub this. name = object. name } //更新操作之前,傳入Subject引數(可以不需要該引數),對其進行檢查,保證發出通知的目標是該observer的目標 update( sub: ConcreteSubject) { //todo you code with new state if( sub === this. subject) console. log( `觀察者 ${ this. name } 觀察的目標subject的state發生了變化`, this. subject. getState()) } }

import Subject from './Subject' import Observer from './Observer' export default class ConcreteSubject extends Subject { state: number constructor () { super() this. state = 0 } getState () { return this. state } setState ( state: number) { let changeStatus = state === this. state this. state = state if (! changeStatus) this. notify() } attach( obj: Observer){ super. attach( obj) } dettach( obj: Observer){ super. dettach( obj) } notify() { this. getObservers(). forEach( observer =>{ observer. update( this) }) } }


最後的測試程式碼demo:

import ConcreteSubject from './ConcreteSubject' import ConcreteObserver from './ConcreteObserver'
// step 1 目標 var newSub = new ConcreteSubject()
// step 2 觀察者 let observer1 = new ConcreteObserver({ sub: newSub, name: 'o1'}) let observer2 = new ConcreteObserver({ sub: newSub, name: 'o2'})
// step 3 將觀察者觀察目標 newSub. attach( observer1) newSub. attach( observer2)
// step 4 目標變化, 觀察結果 newSub. setState( 5)

最後的執行結果是這樣的:

觀察者 o1 觀察的目標subject的state發生了變化 5
觀察者 o2 觀察的目標subject的state發生了變化 5


----------------------------------------------------------------------------------------------------------

擴充套件:

我們還可以顯示地指定感興趣的改變。我們修改Subject的attach方法,為什麼呢,比如我們只想訂閱目標的某一個版塊,就類似於我們微博上面關注了體育 文娛 等模組。

attach(obj: Observer, /* aspect & interest- string or something other*/) 這裡的第二個引數我們擴充套件一下,可以將該觀察者只關心於這一方面的內容。

notify的時候,我們根據 aspect & interest 來呼叫observer的update方法。



參考: 《設計模式:可複用面向物件軟體的基礎》 5.7