Observer Pattern

定義物件間一對多的關係,當一個物件改變狀態,其他相依者都會收到通知並自動被更新。

適用場合

某個物件擁有某些 state,還有一堆其他物件很在乎這些 state 有沒有改變,這些物件在 state 改變時需要做些事情。這邊的 state 是抽象概念,舉凡一個 button 被 click、load data 的 progress、load data 完成與否、畫面是否被修改等等都可以看作 state。另外,一個 object 可能會有很多種 state。

網路上很喜歡用的說法:observer 是一種訂報紙的概念。簡言之,有間報社(subject or observable),要訂報紙的人(observer)跟報社說他要訂報紙,之後報社有新報紙了(狀態改變)就會通知這些訂戶,訂戶拿到報紙後或許拿來看、或許拿來墊便當(?),想幹嘛就幹嘛。如果一個訂戶不想收到新報紙了,就跟報社說一聲退出訂閱,之後報社就不會再通知他了。

再舉另一個例子:UI 操作上滑鼠點了某個東西,程式其他部分需要因應點選做些事情時也適用 observer pattern。

UML

Observer Pattern

Observable 中會有些 state,state 變化時 call NotifyXXX() 通知 observer 們 update。一種做法是 NotifyXXX() call observer 的 update(),而 obsever 會在各自的 update() 中 implement observable 狀態改變時要做的事。

observer 如何取得 observable 的資料?

observer 拿 observable 的資料有兩種方式:

  • push:由 observable 在通知時一起傳給 observer。
  • pull:observer 用 observable 提供的 getter 取得資料。

我是還感覺不出來兩種方式的好壞啦……

有的沒的

  • observer 在 update 時不可以依賴被 notify 的順序,因為不同的 observable 實作可能導致結果不同。
  • Java 裡有現成的 observer pattern API。
  • Java 的 ActionListener 機制是種 observer pattern 的實作──當某件事發生時會 call ActionListener 相應的 function。

Qt signal-slot 轉成 native 寫法

Qt 的 signal-slot 在用途是特定的狀況下,例如某些 signal-slot 是特別針對某個操作時,可以改成自己實作 observer 並取代之。什麼時候會這麼做?不想用 Qt 做某些 notify 甚至不想用 Qt 的時候…

我覺得 observer 跟 listener 差不太多,這裡說的 listener 是稍微簡化的 observer,不會有如上述的 observable interface,而是 listenable 的 class 各自自行增加 listener 相關 member 及 function。

如何將 Qt 的 signal-slot 改成 native 的 listener 寫法、自己處理 notify 呢?

  1. 看懂原本 notify 的流程,區分誰是 listener 誰是 listenable。一般來說 signal 的 sender 是 listenable,signal 的 receiver 是 listener。確認要改哪些 signal、slot、connect()、disconnect()。
  2. 在 listenable class 增加 listener list、add 跟 remove listener function。
  3. 在 listener interface 增加 update function,也就是當某件事發生時會被 call 的 function。
  4. 修改 listenable class 的 emit signal,改為 call listener 的 update function 或 NotifyXXX(),由 NotifyXXX() 再 call listener 的 update function。如果有多處需要通知 listener,建議寫成 NotifyXXX()。如果只有一個地方需要通知,也可以不用 NotifyXXX()。
  5. listener class 繼承 listener interface 並 implement update function。將原本的 slot 改為一般 function,接著看要由 update function call 還是直接 rename 為 update function。如果原本是 auto 或 queued connection 要注意 thread issue。
  6. 修改 listener class,將 connect() 及 disconnect() 改為 call add 及 remove observer。
  7. 需注意 listener 及 listenable object 的生命週期,如果 listenable 活得比 listener 久,listener 死之前要將自己從 listenable 的 listener list 中移除。

Qt 的 signal connection 有些特殊功能也要一併處理:

  • queued connection 可能隱含換 thread,需釐清原本程式是否意圖使用換 thread 機制。
    如果有換 thread 就不是單純 observer pattern 可以處理的,可能要改用 thread + event loop,或 listener 的部分可以使用 Qt 則在 listener 以 signal-slot 換 thread。
  • direct connection 幾乎等同直接 call function,不需要額外處理。
  • unique connection 代換作法之一是在 register observer 時做 filter,已經是 observer 的 object 就不再加入 observer list。

Ref