觀察者模式
這里面綜合了幾本書的資料.
需求
有這么個項目:
需求是這樣的:
一個氣象站, 有三個傳感器(溫度, 濕度, 氣壓), 有一個WeatherData對象, 它能從氣象站獲得這三個數據. 還有三種設備, 可以按要求展示氣象站的最新數據.
WeatherData的結構如下:
有3個get方法, 分別獲取最新的氣溫, 濕度和氣壓. 還有一個measurementsChanged()方法, 當任一傳感器有變化的時候, 這個方法都會被調用.
總結一下項目的需求:
- WeatherData類有三個get方法可以獲取溫度, 濕度和氣壓
- 如果任何一個數據發生變化, 那么measureChanged()方法就會被調用
- 我們需要實現這三種顯示設備:
- 當前天氣
- 數據統計
- 天氣預測
- 系統必須可以擴展, 其他開發者可以創建自定義展示設備.
初版代碼
這個地方有個"錯誤", xxxDisplay都是具體的實現, 而編程規則要求是應該對接口編程而不是對實現編程.
那么什么是觀察者模式?
舉一個例子:
- 報社發行報紙
- 你訂閱報紙, 一旦有新一期的報紙發行, 新報紙就會送到你家里, 只要你一直訂閱, 你就一直會收到新報紙
- 你不再訂閱報紙的時候, 就收不到以后的新報紙了
- 報社運營的時候, 一直會有人去訂閱或者取消訂閱報紙.
發布者 + 訂閱者 = 觀察者模式
Publishers + Subscribers = Observer Pattern
在觀察者模式里, 我們把報社叫做被觀察對象(Subject), 把訂閱者叫做觀察者(Observers)
觀察者模式是這樣操作的:
觀察者模式的定義就是:
一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。
類圖如下:
談一下松耦合
當兩個對象是松耦合的時候, 他們可以進行交互, 但是卻幾乎不了解對方.
觀察者模式下的被觀察者(Subject)和觀察者(Observers)就是松耦合設計的對象. 這是因為:
- 被觀察者(Subject)只知道觀察者實現了某個接口
- 可以隨時添加觀察者
- 添加新類型觀察者的時候不需要修改被觀察者
- 可以復用觀察者或者被觀察者
- 如果被觀察者或觀察者發生變化了, 那么這些變化不會影響到對方.
一個設計原則:
交互的對象之間應盡量設計成松耦合的. Strive for loosely coupled designs between objects that interact.
松耦合設計可以讓我們設計出這樣的系統: 因為對象之間的相互依存減小了, 所以系統可以輕松處理變化.
重新設計:
代碼:
OK, 上面是書中的內容, C#7.0里面對觀察者模式是怎么實現的呢?
先只談下面這個:
Event
談到Event, 就得把delegate先細說一下
Delegate 委托
一個委托類型定義了某種類型的方法(方法的返回類型和參數類型), 然后這個委托的實例可以調用這些方法.
例如:
delegate int Transformer (int x);
這個委托就和返回類型是int, 參數是一個int的方法兼容.
例如:
static int Square (int x) { return x * x }; // 或 static int Square (int x) => x * x;
把一個方法賦值給委托變量的時候就創建了一個委托的實例:
Transformer t = Square;
然后就可以像方法一樣進行調用:
int answer = t(3); // 9
所以說一個委托的實例就是調用者的委托: 調用者調用委托, 然后委托調用目標方法, 這樣就把調用者和目標方法解耦了.
其中:
Transformer t = Square; // 是下面的簡寫 Transformer t = new Transformer(Square);
t(3) // 是下面的簡寫 t.Invoke(3)
多播委托
一個委托實例可以引用多個目標方法. 使用+=操作符.
SomeDelegate d = Method1; d += Method2; // 第二行相當於: d = d + Method2;
調用d的時候就會調用Method1和Method2兩個方法.
委托方法的調用順序和它們被添加的順序是一樣的.
使用-=操作符來移除目標方法:
d -= Method1;
這時調用d后只會執行Method2了.
注意: 委托是不可變的 +=/-=實際上是創建了新的委托.
多播委托返回類型
如果多播委托有返回值(非void), 那么調用者只會獲得最后一個被調用方法的返回值.
委托也可以使用泛型:
public delegate T Transformer<T> (T arg);
Func 和 Action
記住Func有返回值, Action沒有就行.
Event
使用委托的時候, 通常會有兩個角色出現: 廣播者(被觀察者)和訂閱者(觀察者) [觀察者模式]
廣播者包含一個委托field, 廣播者決定何時廣播, 它通過調用委托進行廣播.
訂閱者就是方法的目標接收者.訂閱者可以決定何時開始和結束監聽, 是通過在廣播者的委托上使用+=和-=操作符來實現的.
訂閱者之間互相不了解, 不干擾.
event就是為上述模型所存在的, 它只把上述模型所必須的功能從委托里暴露出來. 它的主要目的就是防止訂閱者之間相互干擾.
最簡單聲明event的方法就是在委托成員前面加上event關鍵字:
public delegate void SomeChangedHandler(decimal x); public class Broadcaster { public event SomeChangedHandler handler; }
在Broadcaster類里面的代碼, 可以把handler作為委托一樣來用.
在Broadcaster類外邊, 只能對這個event執行+=和-=操作.
Event 模式/ 觀察者模式
這種模式在.net core里首先需要EventArgs.
EventArgs是一個基類, 它可以為event傳遞信息.
可以創造它的子類來傳遞自定義參數:
public class FallsIllEventArgs : EventArgs { public readonly string Address; public FallsIllEventArgs(string address) { this.Address = address; } }
然后就需要給這個event定義一個委托了, 這有三條規則:
- 返回類型必須是void
- 需要有兩個參數, 第一個是object, 第二個是EventArgs的子類. 第一個參數代表着廣播者, 第二個參數包含額外的需要傳遞的信息.
- 名稱必須以EventHandler結束.
.net core定義了System.EventHandler<>, 它滿足這些要求.
public event EventHandler<FallsIllEventArgs> FallsIll;
最后, 需要寫一個 protected virtual 方法可以觸發event. 方法的名稱必須和event匹配: 以On開頭, 接受EventArgs類型的參數:
public void OnFallsIll() { FallsIll?.Invoke(this, new FallsIllEventArgs("China Beijing")); }
注意: 預定義的非泛型的EventHandler委托可以在沒有數據需要傳輸的時候使用, 調用的時候可以使用EventArgs.Empty來避免不必要的初始化EventArgs.
用.net core 實現觀察者模式的代碼:
Person.cs
using System; namespace ObserverPattern { public class Person { public event EventHandler<FallsIllEventArgs> FallsIll; public void OnFallsIll() { FallsIll?.Invoke(this, new FallsIllEventArgs("China Beijing")); } } }
using System; namespace ObserverPattern { public class FallsIllEventArgs : EventArgs { public readonly string Address; public FallsIllEventArgs(string address) { this.Address = address; } } }
using System; namespace ObserverPattern { class Program { static void Main(string[] args) { var person = new Person(); person.FallsIll += OnFallsIll; person.OnFallsIll(); person.FallsIll -= OnFallsIll; } private static void OnFallsIll(object sender, FallsIllEventArgs eventArgs) { Console.WriteLine($"A doctor has been called to {eventArgs.Address}"); } } }