2021年7月27日 星期二

[設計模式] Delegate & Event 使用委派與事件來實作 Observer Pattern 觀察者模式

前一篇 Observer Pattern 觀察者模式 提到一些可以透過 Delegate 與 Event 克服的問題,

最主要去克服的概念就是 " Subject 通知者 與 Observer 觀察者 耦合在一起" ,

白話一點要克服的就是" Subject 的視野必須有 Observer ,Observer 的視野也必須有 Subject "。

這篇就來示範如何操作 Delegate Event 來解耦,程式碼請參考 Chapter_14.2

先來看看如果我們使用 Delegate 與 Event 的話,專案省去了哪些東西。


可以發現使用了Delegate之後(右邊Chapter_14.2),便不需要 Subject 與 Observer 介面了,

自然就沒有兩者耦合能不能夠實作介面的問題。


情境

每當 Boss 回到辦公室的時候,都習慣會"我回來了",佛心老闆,

而摸魚的兩位 employee 每次只要聽到老闆"我回來了",就會把眼前的影片關掉

由於這個的人可能是 Boss,也有可能是好心的 Secretary 通風報信,

上述的""就是通知者發出通知的動作,

"關掉"就是觀察者做出回應的動作。


兩種做法

這次除了Delegate之外,也想使用微軟已經定義好的 EventHandler 進行示範,

所以先拿 Boss 來實作 Delegate 與 Event,後面用 Secretary 類別說明什麼是 EventHandler。


在建立Event之前,先建立要傳遞的參數類別

這一次的情境中,我們的 通知者 是會挾帶訊息的,因此 Event 必須透過 EventArgs 挾帶訊息,

首先在每個檔案都看的到的地方定義一個類別作為事件參數,這次是和 Boss 類別放在一起,

類別中定義了要挾帶的資料與型態,且這個類別必須繼承 EventArgs


我在這個 Event 會傳遞的參數類別中只定義一個 String 型別資料,

接著來看Event要如何傳遞它。


實作 Delegate 與 Event

首先先定義 Delegate 型別後方的參數規範了要被委託的 function 應有的格式。


這邊宣告了一個 Delegate 型別 名為 NotifyEventHandler,參數有兩個,

分別是 object 與 notifyObserverEventArgs 型別,

因此要託付給 NotifyEventHandler 的 function都必須符合相同的格式。

[補充紀錄]為什麼常看到 EventArgs 會有 object 參數,簡單假設一種情況,

observer 物件可能同時觀察好幾處通知者,並都用同一個 function 去應對,

這時就能夠透過 object 讓 subject 把自己傳過去,這樣就知道是誰傳遞了這個訊息。



 Delegate 型別 宣告的 Delegate 變數 能夠被託付多個 function,

被託付的 function 會被依序喚起,但這邊不以 Delegate 型別 去宣告變數。

而是以 NotifyEventHandler 型別宣告一個"加了event 關鍵字 的 Delegate 變數 notifyObserver",

通常我們都直接稱他為 event ,但為什麼要加上 event ?

 event 本身就是一種 Delegate,能夠接受託付 function ,

但和 Delegate 最大的不同便是它多了一些限制,下面這句話取自 MSDN

"事件是一種特殊的多點傳送委派,只能從宣告委派的類別或結構內叫用 (發行者類別)。"

白話就是只有宣告 event 變數的類別可以呼叫這個委派,別人都不能呼叫。

因此才會再用 event 關鍵字去宣告變數來加以限制,

不然其實單純使用 Delegate 也能完成這項任務。


註冊事件

到這邊,Boss 類別已經準備好 Event 來讓人註冊了,

讓人註冊的意思就是"把準備好應對的 function 託付給 Boss 的 Event "。

那先來看看 Employee 準備了什麼 function 要託付給 Boss 的 Event 。

兩位 Employee 準備的 function 是相同的,這邊只放其中一位。


Employee 準備了 response() 準備託付給 Boss 的 Event,

特別注意的是框框中的參數部分,必須和 Boss 的 Event 所使用的 Delegate 格式相同。

接著來看如何註冊(託付)。

先把人物準備好之後,透過 += 把想託付的 function 交給 Event 去保管,如下圖框框處。


這邊將 employee 的 response() 託付給 Boss 的 notifyObserver Event,

當 Boss 呼叫 Notify()的時候,便會執行 notifyObserver Event 內被託付的 function,

也就是 employee 的 reponse() ,達到通知的效果。


來看看 Boss 如何實作 Notify()。


呼叫 notifyObserver() 後便會執行 Event 內被委託的所有 funcition。

回顧一下委託給  notifyObserver Event 的 function 順序。


再來看看一下執行結果。

可以發現是照順序 NBA -> Stock 去執行。

到這邊已經完成 Observer Pattern 的所有功能了,相較於不使用 Delegate 與 Event 的情況,

 Boss 不需要去知道是誰正再觀察它自己,解除了 Subject 對 Observer 的耦合

而 Observer 也不需要遵循 Interface 所定義的名稱去命名要對這個 Event 做出回應的 function。

小結論:

只要 Observer 看的到它想觀察的 Subject 類別 的 Event,就能夠進行觀察、接收通知,

以任意的function進行回應。


假如我不需要來自 Subject 的參數該怎辦?

有時候 Subject 並沒有參數可以傳遞,或者是 Observer 也不需要來自 Subject 的參數,

那麼我們可以把 Delegate 定義為無參數的格式,並用它建立 Event 變數,示範如下:


由於 Delegate 是無參數格式的,那麼要委託給這個 Event 的 function 也必須是無參數的,

所以 Employee 的 response() 也必須修改,如下:


使用者端基本上沒什麼差異,這邊只是把 Notify() 從有參數改為無參數的格式。


Notify() 的實作:


呼叫結果:



EventHandler呢?

(礙於篇幅太長,將於下一篇示範EventHandler)

會想介紹無參數的範例與使用 EventHandler 的範例,

是因為我被這些東西搞混了一段時間,找了很多篇資料才比較清楚這些工具之間的差異,

希望能提供給初學 Event 的人更清楚的比較。

沒有留言:

張貼留言

社會新鮮人如何投資?

我的觀點是,在 沒有很多 本錢 的情況下, 別寄望每個月幾千元放到股票或者最近很夯的高股息ETF就能讓你致富, 先投資自己,讓自己的本業收入提高吧。