《消失的設(shè)計模式》系列之觀察者模式

設(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

面向?qū)ο笸暾a

戴上函數(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

函數(shù)式完整代碼

總結(jié)

本著 do not call me, I will call you! 的理念搂漠,觀察者模式可以在其他對象發(fā)生某些變化的時候得到通知迂卢。其本質(zhì)是存儲計算過程,稍后執(zhí)行桐汤,換句話說而克,就是將一些函數(shù)存下來,在適當?shù)臅r候調(diào)用而已怔毛,如此簡單员萍。而越來越多的語言將函數(shù)視為一等對象,所以函數(shù)作為參數(shù)傳入拣度,存儲充活,再執(zhí)行這種模式會非常簡單易用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜡娶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子映穗,更是在濱河造成了極大的恐慌窖张,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚁滋,死亡現(xiàn)場離奇詭異宿接,居然都是意外死亡赘淮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門睦霎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來梢卸,“玉大人,你說我怎么就攤上這事副女「蚋撸” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵碑幅,是天一觀的道長戴陡。 經(jīng)常有香客問我,道長沟涨,這世上最難降的妖魔是什么恤批? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮裹赴,結(jié)果婚禮上喜庞,老公的妹妹穿的比我還像新娘。我一直安慰自己棋返,他們只是感情好延都,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著懊昨,像睡著了一般窄潭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酵颁,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天嫉你,我揣著相機與錄音,去河邊找鬼躏惋。 笑死幽污,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的簿姨。 我是一名探鬼主播距误,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扁位!你這毒婦竟也來了准潭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤域仇,失蹤者是張志新(化名)和其女友劉穎刑然,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暇务,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡泼掠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年怔软,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片择镇。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡挡逼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腻豌,到底是詐尸還是另有隱情家坎,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布饲梭,位于F島的核電站乘盖,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏憔涉。R本人自食惡果不足惜订框,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望兜叨。 院中可真熱鬧穿扳,春花似錦、人聲如沸国旷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跪但。三九已至履羞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屡久,已是汗流浹背忆首。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留被环,地道東北人糙及。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像筛欢,于是被迫代替她去往敵國和親浸锨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容