Design Pattern | 只要你想知道,我就告訴你 - 觀察者模式( Observer Pattern ) feat. TypeScript

神Q超人
Enjoy life enjoy coding
6 min readJul 13, 2019
如果有天忘了自己訂閱了什麼 (來源:Photo by Samuel Zeller on Unsplash

前言

Hi!大家好,我是神 Q 超人。不曉得上一篇的 策略模式 大家感覺如何,就筆者來說,還滿喜歡看 Design Pattern 的,因為它著重在解決問題的方式,我不能說學了 Design Pattern 後,每次打出來的程式碼都超級完美,但是它一定能夠讓你在某個 coding 的 moment 感受到

我也許可以這樣寫?

當這個念頭一閃而過的時候,別懷疑, Do 下去就對了,像五月天教過我們的,

每次衝動留下的 都有所不同
然而有天你會懂 就是那些 讓你不同

那為了讓大家衝動的次數和可能性能夠變多,本文就來提提新的 Design Pattern 。

Observer Pattern (觀察者模式)

當多個 Class 都需要接收同一種資料的變化時,就適合使用 Observer Pattern

上方「多個 Class 」指的就是「觀察者」,而「同一種資料」指的就是觀察者們想了解的「主題」,因此

Observer Pattern 實作的原理就是把獲取資料的部分抽離出來,並在資料改變時,同步送給所有的觀察者。

且觀察者可以在任何時候決定是否要繼續接收資料。

以 Youtuber 來說,他們會在所有影片的最後或一開始告訴大家說:「如果喜歡我的影片,記得幫我按讚分享,別忘了開啟小鈴鐺訂閱我哦!」。

這個訂閱的功能就像「主題」與「觀察者」之間的關係,只要你開啟訂閱,系統就會把你加進「訂閱者」的清單,在新影片發佈時,會將通知送給清單內的人,當然如果有一天,訂閱者不再喜歡這些影片,也可以隨時取消訂閱,只要被移出「訂閱者」清單,就不會再收到通知。

Observer Pattern 實作流程

以下用幾個流程來完成一個簡單的 Observer Pattern 。

找出「主題」和「觀察者們」

就上方的例子來說,「 Youtuber 」就是主題,「喜歡影片而訂閱的人 」就是觀察者。

設計觀察者的 Interface

這裡先設計 Interface 有幾個原因:

  1. 因為觀察者不只一個,為了讓所有的觀察者都能夠被主題「加入訂閱」和「移除訂閱」,它們就得擁有同一個 Interface ,當執行事件時,就只需要以 Interfacet 做共同型別代替 Class 傳入。
  2. 觀察者需要被通知,用 Interface 可以確保所有 Class 都有接受通知的行為存在。

雖然感覺很複雜,但是實際上,在觀察者的 Interface 內,只需要確保有接受通知的行為就可以了:

設計主題的 Interface

主題是 Youtuber ,它會需要三個行為,分別是「加入訂閱」、「移除訂閱」、「通知訂閱的人」:

這裡就可以看見 IObserver 的用途了,如果所有的訂閱者沒有同一個 Interface ,那這裡就會變得很複雜,更詳細的可以參照 這篇文章 內提到關於 Interface 的應用。

以 IYoutuber 實作 Class

上方不斷地提到「訂閱」這個功能,但是究竟要把訂閱的人保存在哪裡呢?就直接放到 Class 的 Private 屬性中吧!當然這個屬性的型別是 Array 陣列,而 Array 內存放的型別就是 IObserver

接下來,關於「訂閱」和「取消訂閱」這兩個功能,就是將送進來的參數加入或從陣列移除,而負責送通知給訂閱者的 notifyObserver ,則是用 forobservers 內的所有訂閱者都取出來,執行 update

上方的 notifyObserver 也因為 observers 內的型別都是 IObserver ,所以一定會有 update 能執行, Interface 真的太猛了。

目前的 Youtuber 內有了幾個主要的行為,但這樣有點不夠真實,因此我再為他們加上了 namepublishVideo ,待會就可以創建各個不同的 Youtuber 然後透過 publishVideo 假裝發佈新影片,然後用 notifyObserver 做通知,完成後的 Youtuber 會長這樣:

以 IObserver 實作 Observer

這裡比較輕鬆,只需要確認 Observer 能夠有 update 就好,其他事情則是在 update 處理:

完成 YoutuberObserver 後,就能來看看怎麼使用它們:

執行結果如下:

如果取消訂閱,不再接收任何通知也非常簡單,只需調用 removeObservers

從執行結果可以看到取消訂閱後,當 aGa 再執行 publishVideo 時,observer 就不會得到通知,因為 observer 已經不在 aGa 內部的訂閱者清單中了:

結論

  1. Interface 真的很偉大。
  2. 將負責處理資料的部分抽到 Youtuber ,當有新影片就主動通知所有訂閱的人,而不是由訂閱人一直問說有沒有新影片了。
  3. 耦合度很低,因為只要是實作了 IObserver 接口都可以接收新影片,而不是特定某個 Class 或是 Object 做型別參數,傳入了才發現沒有 update 的行為。
  4. Youtuber 不管誰收到通知後要幹嘛,因為它只負責通知,上方是以觀影者的角度接收通知,但其實不只觀影者,就算是官方負責統計資料的 Class ,只要它有實作 IObserver 接口,那就能和觀影者同步接收到新影片的通知,各自處理接到資料後的邏輯。

原本以為 Observer Pattern 會很容易說明,結果發現真的很困難啊 😭 ,尤其是在解說例子的部分,如果對文章有任何建議、問題、或是需要改進的地方再麻煩各位留言告訴我,謝謝大家!

參考文章

  1. 深入淺出設計模式 (Head First Design Patterns)

--

--