今天要談到的觀察者模式也是很常見的一個模式,常出現在有兩個以上需要互相溝通的物件之間
假設有個物件 A 想要獲得物件 B 的更新資訊,但實際上 A 不知道 B 哪時候才會更新。這時候 A 可以做的事情就是,不斷地向 B 發出請求然後拿回資訊。或者,就讓 B 把資訊廣播到系統當中,讓所有系統當中的物件都可以接收到最新的資訊。
但以上兩種都不是理想的做法。最好的情況會是,B 知道 A 在等他,所以一旦更新資訊的時候,B 就可以主動通知 A。
以現實生活中的例子來說,就是某個消費者想要獲得某個品牌的最新資訊,就會加入這個品牌的會員。而當這個品牌有新產品上市或有新的折扣的時候,就會找出他的「會員名單」,然後一一通知他們
在觀察者模式當中有兩個主體,分別是 Subject
和 Observer
,可以想像就是先前提到的「會發出通知的品牌」和「想要收到通知的消費者」
所以在 Subject
當中,有可以將消費者加入名單,或從名單中移除的方法。在 Observer
當中,則有個 update
方法可以呼叫
interface Subject {
attach(observer: Observer): void
detach(observer: Observer): void
notify(): void
}
interface Observer {
update(subject: Subject): void;
}
接下來就讓我們根據介面來建立類別。
這裡我就不用剛剛的品牌和消費者的例子,而是建立一個特務機構 SecretIntelligenceService
,並實作出各種方法的細節
class SecretIntelligenceService implements Subject {
private observers: Observer[] = [];
message: string;
attach(observer: Observer): void {
if (this.observers.includes(observer)) {
return
}
this.observers.push(observer)
}
detach(observer: Observer): void {
this.observers = this.observers.filter(o => o !== observer)
}
notify(): void {
this.observers.forEach(observer => observer.update(this))
}
updateInfo(message: string): void {
this.message = message
this.notify()
}
}
而在 Observer
這邊,我們建立了 Agent
類別,以及 update
方法的細節
class Agent implements Observer {
name: string
constructor(name: string) {
this.name = name
}
update(info: SecretIntelligenceService): void {
console.log(`${this.name} received update: ${info.message}`)
}
}
那麼最後,我們就可以來建立實例
const mi6 = new SecretIntelligenceService()
const james = new Agent('james')
const bond = new Agent('bond')
mi6.attach(james)
mi6.attach(bond)
mi6.attach(james) // james 不會被重複加入
實際執行結果如下:
mi6.updateInfo('no time to die')
// james received update: no time to die
// bond received update: no time to die
mi6.detach(james)
mi6.updateInfo('shaken, not stirred')
// bond received update: shaken, not stirred
觀察者模式解決了我們先前提到的問題,並在物件之間建立起溝通的管道,讓物件之間的合作能夠更為順暢。只不過目前所有的 obervers 都會同時接收到更新資訊,如果我們希望 obervers 有層級之分、有先後收到資訊的分別,那麼就需要額外的處理,或是使用其他的模式來解決問題了