C# 上 Observer pattern 的實作差異 - observer pattern on C#
前陣子有注意到,大多數的書籍或網站資料在介紹 設計模式(Design Pattern) 時,往往所採用的實作方式都是以 C++ 或 JAVA 為實作基礎,甚至在 Unity 為主題的書籍上,也沒有針對 C# 做進一步說明。
事實上作為一個不斷擴充版本及功能的語言,.NET 的 C# 的使用上提供了更多樣化的類別或介面,可以用不太一樣的方式來實作設計模式,妥善的應用 C# 所帶來的便利。
最明顯的例子,就是利用 C# 的 delegate,可以更加方便的實作 觀察者模式(Observer pattern),甚至讓應用上更加靈活。
本文記錄了三種作法可讓大家比較參考:
- 純 class
- Delegate (Action)
- Event & EventHandler
純 class
1 | public abstract class Subject { |
這是一般很常見的範例內容,其中我加上了 System.Object 作為發出通知時可以額外傳送資料的欄位。
如果想避免使用 System.Object 造成的 Boxing 消耗,可以改用泛型來設計類別:
1 | public abstract class Subject<T> { |
加上泛型,可以避免掉 Boxing 的消耗,但同時也會犧牲掉 Subject 類別的使用彈性,不再能使用 List 的方式來管理大量的主題。
Delegate (Action)
1 | public class SubjectWithDelegate<T> { |
Delegate 是在 C# 相當泛用且強大的功能,先接觸 C++ 的我在一開始並無法靈活使用 Delegate,但是熟悉了之後就不想回去寫要自行管理函式指標的 C++ 了。
利用 Delegate 來實作 觀察者模式(Observer pattern),可以硬生生的省去一個 Observer 類別。不只減少開發上要不斷繼承的需求,也可以更加靈活的在各處使用觀察者 (畢竟只是再多寫個函式),在開發 Unity 時可以使用 C# 真是太好了。
Event & EventHandler
1 | public class SubjectWithEvent<T> where T : System.EventArgs { |
再更進一步使用 event 的關鍵字來封裝 Delegate,可以再省去兩個方法的實作,同時因為 C# 的 event 關鍵字對 public delegate member 做了安全上的限制,也不怕會被外部類別誤用。關於 event 的說明可以在我的一篇文章中看到:
同時這段程式碼使用了 EventHandler 來代替 Action,如此一來可以使用 EventArgs 這個 .NET 內建的類別作為事件資訊的基礎類別,使類別的架構更加符合設計原則。
不過使用 EventHandler 有個缺點,他是一個 Action<Object, EventArgs> 形式的 Delegate,所以運作上會有 Boxing 消耗,是否使用需依照情況進行斟酌。
其他
介紹了三個方法,每個方法都有各自的優點跟缺點,目前我最常使用的是混和了第二跟第三種方法的設計,即使用 event 關鍵字但不使用 EventHandler。
同為設計模式,有很多不同的實作方式可以因使用的語言而產生差異,如何同時善用語言特性跟設計模式,則需要不斷嘗試跟研究,希望我的一些心得可以帶給大家幫助。
.NET 甚至還有提供以觀察者為名的介面可以直接利用,不過因為我自己沒有使用過,便不多做討論,僅提供官方的文件連結做為參考:
- System.IObservable 和 System.IObserver - https://msdn.microsoft.com/zh-tw/library/ee850490(v=vs.110).aspx