C# 观察者模式 以及 delegate 和 event
觀察者模式
這里面綜合了幾本書的資料.
需求
有這么個(gè)項(xiàng)目:?
需求是這樣的:
一個(gè)氣象站, 有三個(gè)傳感器(溫度, 濕度, 氣壓), 有一個(gè)WeatherData對(duì)象, 它能從氣象站獲得這三個(gè)數(shù)據(jù). 還有三種設(shè)備, 可以按要求展示氣象站的最新數(shù)據(jù).
WeatherData的結(jié)構(gòu)如下:
有3個(gè)get方法, 分別獲取最新的氣溫, 濕度和氣壓. 還有一個(gè)measurementsChanged()方法, 當(dāng)任一傳感器有變化的時(shí)候, 這個(gè)方法都會(huì)被調(diào)用.
總結(jié)一下項(xiàng)目的需求:
WeatherData類有三個(gè)get方法可以獲取溫度, 濕度和氣壓
如果任何一個(gè)數(shù)據(jù)發(fā)生變化, 那么measureChanged()方法就會(huì)被調(diào)用
我們需要實(shí)現(xiàn)這三種顯示設(shè)備:
當(dāng)前天氣
數(shù)據(jù)統(tǒng)計(jì)
天氣預(yù)測(cè)
系統(tǒng)必須可以擴(kuò)展, 其他開(kāi)發(fā)者可以創(chuàng)建自定義展示設(shè)備.
初版代碼
這個(gè)地方有個(gè)"錯(cuò)誤", xxxDisplay都是具體的實(shí)現(xiàn), 而編程規(guī)則要求是應(yīng)該對(duì)接口編程而不是對(duì)實(shí)現(xiàn)編程.
那么什么是觀察者模式?
舉一個(gè)例子:
報(bào)社發(fā)行報(bào)紙
你訂閱報(bào)紙, 一旦有新一期的報(bào)紙發(fā)行, 新報(bào)紙就會(huì)送到你家里, 只要你一直訂閱, 你就一直會(huì)收到新報(bào)紙
你不再訂閱報(bào)紙的時(shí)候, 就收不到以后的新報(bào)紙了
報(bào)社運(yùn)營(yíng)的時(shí)候, 一直會(huì)有人去訂閱或者取消訂閱報(bào)紙.
發(fā)布者 + 訂閱者 = 觀察者模式
Publishers + Subscribers = Observer Pattern
在觀察者模式里, 我們把報(bào)社叫做被觀察對(duì)象(Subject), 把訂閱者叫做觀察者(Observers)
觀察者模式是這樣操作的:
??
觀察者模式的定義就是:
一個(gè)目標(biāo)物件管理所有相依于它的觀察者物件,并且在它本身的狀態(tài)改變時(shí)主動(dòng)發(fā)出通知。
類圖如下:
?
談一下松耦合
當(dāng)兩個(gè)對(duì)象是松耦合的時(shí)候, 他們可以進(jìn)行交互, 但是卻幾乎不了解對(duì)方.
觀察者模式下的被觀察者(Subject)和觀察者(Observers)就是松耦合設(shè)計(jì)的對(duì)象. 這是因?yàn)?
被觀察者(Subject)只知道觀察者實(shí)現(xiàn)了某個(gè)接口
可以隨時(shí)添加觀察者
添加新類型觀察者的時(shí)候不需要修改被觀察者
可以復(fù)用觀察者或者被觀察者
如果被觀察者或觀察者發(fā)生變化了, 那么這些變化不會(huì)影響到對(duì)方.
一個(gè)設(shè)計(jì)原則:
交互的對(duì)象之間應(yīng)盡量設(shè)計(jì)成松耦合的.?Strive for loosely coupled designs between objects that interact.
松耦合設(shè)計(jì)可以讓我們?cè)O(shè)計(jì)出這樣的系統(tǒng): 因?yàn)閷?duì)象之間的相互依存減小了, 所以系統(tǒng)可以輕松處理變化.
重新設(shè)計(jì):
代碼:
OK, 上面是書中的內(nèi)容, C#7.0里面對(duì)觀察者模式是怎么實(shí)現(xiàn)的呢?
先只談下面這個(gè):
Event
談到Event, 就得把delegate先細(xì)說(shuō)一下
Delegate 委托
一個(gè)委托類型定義了某種類型的方法(方法的返回類型和參數(shù)類型), 然后這個(gè)委托的實(shí)例可以調(diào)用這些方法.
例如:
delegate int Transformer (int x);這個(gè)委托就和返回類型是int, 參數(shù)是一個(gè)int的方法兼容.
例如:
static int Square (int x) { return x * x };// 或static int Square (int x) => x * x;?
把一個(gè)方法賦值給委托變量的時(shí)候就創(chuàng)建了一個(gè)委托的實(shí)例:
Transformer t = Square;?
然后就可以像方法一樣進(jìn)行調(diào)用:
int answer = t(3); // 9?
所以說(shuō)一個(gè)委托的實(shí)例就是調(diào)用者的委托: 調(diào)用者調(diào)用委托, 然后委托調(diào)用目標(biāo)方法, 這樣就把調(diào)用者和目標(biāo)方法解耦了.
其中:
Transformer t = Square;// 是下面的簡(jiǎn)寫Transformer t = new Transformer(Square);?
t(3)// 是下面的簡(jiǎn)寫t.Invoke(3)?
多播委托
一個(gè)委托實(shí)例可以引用多個(gè)目標(biāo)方法. 使用+=操作符.
SomeDelegate d = Method1; d += Method2;// 第二行相當(dāng)于:d = d + Method2;?
調(diào)用d的時(shí)候就會(huì)調(diào)用Method1和Method2兩個(gè)方法.
委托方法的調(diào)用順序和它們被添加的順序是一樣的.
使用-=操作符來(lái)移除目標(biāo)方法:
d -= Method1;?
這時(shí)調(diào)用d后只會(huì)執(zhí)行Method2了.
注意: 委托是不可變的 +=/-=實(shí)際上是創(chuàng)建了新的委托.
多播委托返回類型
如果多播委托有返回值(非void), 那么調(diào)用者只會(huì)獲得最后一個(gè)被調(diào)用方法的返回值.
委托也可以使用泛型:
public delegate T Transformer<T> (T arg);?
Func 和 Action
記住Func有返回值, Action沒(méi)有就行.
?
Event
使用委托的時(shí)候, 通常會(huì)有兩個(gè)角色出現(xiàn): 廣播者(被觀察者)和訂閱者(觀察者) [觀察者模式]
廣播者包含一個(gè)委托field, 廣播者決定何時(shí)廣播, 它通過(guò)調(diào)用委托進(jìn)行廣播.
訂閱者就是方法的目標(biāo)接收者.訂閱者可以決定何時(shí)開(kāi)始和結(jié)束監(jiān)聽(tīng), 是通過(guò)在廣播者的委托上使用+=和-=操作符來(lái)實(shí)現(xiàn)的.
訂閱者之間互相不了解, 不干擾.
event就是為上述模型所存在的, 它只把上述模型所必須的功能從委托里暴露出來(lái). 它的主要目的就是防止訂閱者之間相互干擾.
最簡(jiǎn)單聲明event的方法就是在委托成員前面加上event關(guān)鍵字:
public delegate void SomeChangedHandler(decimal x);
public class Broadcaster
{
? ? public event SomeChangedHandler handler;
}
在Broadcaster類里面的代碼, 可以把handler作為委托一樣來(lái)用.
在Broadcaster類外邊, 只能對(duì)這個(gè)event執(zhí)行+=和-=操作.
?
Event 模式/ 觀察者模式
這種模式在.net core里首先需要EventArgs.
EventArgs是一個(gè)基類, 它可以為event傳遞信息.
可以創(chuàng)造它的子類來(lái)傳遞自定義參數(shù):
public class FallsIllEventArgs : EventArgs
? ? {
? ? ? ? public readonly string Address;
? ? ? ? public FallsIllEventArgs(string address)
? ? ? ? {
? ? ? ? ? ? this.Address = address;
? ? ? ? }
? ? }
然后就需要給這個(gè)event定義一個(gè)委托了, 這有三條規(guī)則:
返回類型必須是void
需要有兩個(gè)參數(shù), 第一個(gè)是object, 第二個(gè)是EventArgs的子類. 第一個(gè)參數(shù)代表著廣播者, 第二個(gè)參數(shù)包含額外的需要傳遞的信息.
名稱必須以EventHandler結(jié)束.
.net core定義了System.EventHandler<>, 它滿足這些要求.
public event EventHandler<FallsIllEventArgs> FallsIll;?
最后, 需要寫一個(gè) protected virtual 方法可以觸發(fā)event. 方法的名稱必須和event匹配: 以O(shè)n開(kāi)頭, 接受EventArgs類型的參數(shù):
public void OnFallsIll(){FallsIll?.Invoke(this, new FallsIllEventArgs("China Beijing"));}?注意: 預(yù)定義的非泛型的EventHandler委托可以在沒(méi)有數(shù)據(jù)需要傳輸?shù)臅r(shí)候使用, 調(diào)用的時(shí)候可以使用EventArgs.Empty來(lái)避免不必要的初始化EventArgs.
?
用.net core 實(shí)現(xiàn)觀察者模式的代碼:
Person.cs
using System;
namespace ObserverPattern
{
? ? public class Person
? ? {
? ? ? ? public event EventHandler<FallsIllEventArgs> FallsIll;
? ? ? ? public void OnFallsIll()
? ? ? ? {
? ? ? ? ? ? FallsIll?.Invoke(this, new FallsIllEventArgs("China Beijing"));
? ? ? ? }
? ? }
}
FallsIllEventArgs.cs:
using System;
namespace ObserverPattern
{
? ? public class FallsIllEventArgs : EventArgs
? ? {
? ? ? ? public readonly string Address;
? ? ? ? public FallsIllEventArgs(string address)
? ? ? ? {
? ? ? ? ? ? this.Address = address;
? ? ? ? }
? ? }
}
Program.cs:
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}");
? ? ? ? }
? ? }
}
原文地址?https://www.cnblogs.com/cgzl/p/8688476.html
.NET社區(qū)新聞,深度好文,歡迎訪問(wèn)公眾號(hào)文章匯總 http://www.csharpkit.com
總結(jié)
以上是生活随笔為你收集整理的C# 观察者模式 以及 delegate 和 event的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Slickflow.NET 开源工作流引
- 下一篇: Serilog Tutorial