設(shè)計模式是面向?qū)ο蟮挠杏霉ぞ咛友樱蔷幊陶Z言的發(fā)展和多種編程范式混合編程的可能,使很多的模式被語言特性取代轧拄,或者被其他編程范式解決揽祥。
要解決的問題
假如你想創(chuàng)建一個機器人,在發(fā)布文章的時候紧帕,自動同步到知乎盔然、簡書等其他的平臺。在這里是嗜,我們有 3 個實體愈案,一個是發(fā)布文章,在該事件發(fā)生的時候鹅搪,分別通知知乎和簡書的實體進行同步站绪。同時為了可擴展性,需要支持可能的其他的平臺丽柿。為了解決這類問題恢准,我們可以使用觀察者模式。
定義
在對象之間定義一對多的依賴甫题,當一個對象改變時馁筐,依賴它的對象都會收到通知,并自動更新坠非。
面向?qū)ο蟮姆绞?/h2>
UML
observer-uml.png
如上圖所示敏沉,
-
Subject
接口定義了主題的行為,在我們的例子中炎码,是發(fā)布文章盟迟。它持有一系列遵循Observer
接口的實例×氏校可以通過registerObserver
進行添加攒菠,removeObser
進行移除,最終通過notifyObservers
通知所有的觀察者 -
Observer
接口定義了觀察者的行為
代碼
首先定義 Observer
接口:
interface Observer {
update(newBlog: string)
}
包含一個 update
方法歉闰。
接下來分別定義兩個 Observer
:
class ZhihuObserver implements Observer {
update(newBlog) {
console.log('publishing to zhihu...', newBlog)
}
}
class JianshuObserver implements Observer {
update(newBlog) {
console.log('publishing to jianshu...', newBlog)
}
}
在收到通知(update
方法被調(diào)用)的時候辖众,將新文章的內(nèi)容打印出來卓起。
定義 Subject
接口:
interface Subject {
registerObserver(o: Observer)
removeObserver(o: Observer)
notifyObservers(newBlog: string)
}
這里由于我們需要告知觀察者新文章的內(nèi)容,notifyObservers
接受了一個 string
類型的參數(shù)赵辕。
class BlogWriter implements Subject {
private observers: Observer[] = []
registerObserver(o: Observer) {
this.observers.push(o)
}
removeObserver(o: Observer) {
this.observers = this.observers.filter(v => v !== o)
}
notifyObservers(blog: string) {
this.observers.forEach(o => {
o.update(blog)
})
}
}
BlogWriter
類實現(xiàn)了 Subject
接口:
- 擁有一個私有的
observers
來存儲已注冊的 Observer既绩。 -
registerObserver
方法將一個 Observer 存儲到observers
數(shù)組中 -
removeObserver
方法將指定的 Observer 移除 -
notifyObservers
方法通知所有的observers
(調(diào)用其update
方法)
來看運行效果:
const subject: Subject = new BlogWriter()
const zhihu = new ZhihuObserver()
subject.registerObserver(zhihu)
subject.registerObserver(new JianshuObserver())
subject.notifyObservers('hello')
// publishing to zhihu... hello
// publishing to jianshu... hello
subject.removeObserver(zhihu)
subject.notifyObservers('world')
// publishing to jianshu... world
戴上函數(shù)式的思考帽
其實問題的本質(zhì)是將一系列的執(zhí)行過程存儲起來,在特定事件發(fā)生的時候还惠,執(zhí)行這些過程饲握。
我們來修改 Observer
的定義:
type Observer = (newBlog: string) => void
非常直白,就是一個函數(shù)定義蚕键。那么對應的 Observer
就可以改成:
const zhihuObserver: Observer = newBlog => {
console.log('publishing to zhihu...', newBlog)
}
const jianshuObserver: Observer = newBlog => {
console.log('publishing to jianshu...', newBlog)
}
就是兩個函數(shù)定義而已救欧。此時 BlogWriter
類變成了:
class BlogWriter implements Subject {
private observers: Observer[] = []
registerObserver(o: Observer) {
this.observers.push(o)
}
removeObserver(o: Observer) {
this.observers = this.observers.filter(v => v !== o)
}
notifyObservers(blog: string) {
this.observers.forEach(o => {
o(blog)
})
}
}
實際上,我們可以更進一步锣光,去掉類的枷鎖:
const createBlogWriter = (): Subject => {
let observers: Observer[] = []
return {
registerObserver: (o: Observer) => {
observers.push(o)
},
removeObserver: (o: Observer) => {
observers = observers.filter(v => v !== o)
},
notifyObservers(blog: string) {
observers.forEach(o => o(blog))
},
}
}
這里我們創(chuàng)建了一個函數(shù)笆怠,createBlogWriter
該函數(shù)的返回值是一個實現(xiàn)了 Subject
接口的對象。代碼邏輯和之前面向?qū)ο蟮姆绞较嗤艿贿^這里我們使用了閉包來承擔私有變量的作用蹬刷。來看最終的運行效果:
const subject = createBlogWriter()
subject.registerObserver(zhihuObserver)
subject.registerObserver(jianshuObserver)
subject.notifyObservers('hello')
// publishing to zhihu... hello
// publishing to jianshu... hello
subject.removeObserver(zhihuObserver)
subject.notifyObservers('world')
// publishing to jianshu... world
我們再來看一遍函數(shù)式的代碼:
const createBlogWriter = (): Subject => {
let observers: Observer[] = []
return {
registerObserver: (o: Observer) => {
observers.push(o)
},
removeObserver: (o: Observer) => {
observers = observers.filter(v => v !== o)
},
notifyObservers(blog: string) {
observers.forEach(o => o(blog))
},
}
}
非常簡潔。而這里真正的強大之處在于频丘,Observer
只是一個函數(shù)類型办成,任何接收一個類型為 string -> void
的函數(shù)都可以作為 Observer
!
總結(jié)
本著 do not call me, I will call you! 的理念搂漠,觀察者模式可以在其他對象發(fā)生某些變化的時候得到通知迂卢。其本質(zhì)是存儲計算過程,稍后執(zhí)行桐汤,換句話說而克,就是將一些函數(shù)存下來,在適當?shù)臅r候調(diào)用而已怔毛,如此簡單员萍。而越來越多的語言將函數(shù)視為一等對象,所以函數(shù)作為參數(shù)傳入拣度,存儲充活,再執(zhí)行這種模式會非常簡單易用。