九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
三種觀察者模式的C#實現(xiàn)



說起觀察者模式,估計在園子里能搜出一堆來。所以寫這篇博客的目的有兩點:


  • 觀察者模式是寫松耦合代碼的必備模式,重要性不言而喻,拋開代碼層面,許多組件都采用了Publish-Subscribe模式,所以我想按照自己的理解重新設計一個使用場景并把觀察者模式靈活使用在其中

  • 我想把C#中實現(xiàn)觀察者模式的三個方案做一個總結(jié),目前還沒看到這樣的總結(jié)


現(xiàn)在我們來假設這樣的一個場景,并利用觀察者模式實現(xiàn)需求:


場景:未來智能家居進入了每家每戶,每個家居都留有API供客戶進行自定義整合,所以第一個智能鬧鐘(smartClock)先登場,廠家為此鬧鐘提供了一組API,當設置一個鬧鈴時間后該鬧鐘會在此時做出通知,我們的智能牛奶加熱器,面包烘烤機,擠牙膏設備都要訂閱此鬧鐘鬧鈴消息,自動為主人準備好牛奶,面包,牙膏等。


這個場景是很典型的觀察者模式,智能鬧鐘的鬧鈴是一個主題(subject),牛奶加熱器,面包烘烤機,擠牙膏設備是觀察者(observer),觀察者只需要訂閱這個主題即可實現(xiàn)松耦合的編碼模型,讓我們通過三種方案逐一實現(xiàn)此需求。


一、利用.net的Event模型來實現(xiàn)


.net中的Event模型是一種典型的觀察者模型,在.net出身之后被大量應用在了代碼當中,我們看事件模型如何在此種場景下使用,


首先介紹下智能鬧鐘,廠家提供了一組很簡單的API


public void SetAlarmTime(TimeSpan timeSpan)

{

_alarmTime = _now().Add(timeSpan);

RunBackgourndRunner(_now, _alarmTime);

}


SetAlarmTime(TimeSpan timeSpan)用來定時,當用戶設置好一個時間后,鬧鐘會在后臺跑一個類似于while(true)的循環(huán)對比時間,當鬧鈴時間到了后要發(fā)出一個通知事件出來


protected void RunInBackgournd(Func now,DateTime? alarmTime )

{

if (alarmTime.HasValue)

{

var cancelToken = new CancellationTokenSource();

var task = new Task(() =>

{

while (!cancelToken.IsCancellationRequested)

{

if (now.AreEquals(alarmTime.Value))

{

//鬧鈴時間到了

ItIsTimeToAlarm();

cancelToken.Cancel();

}

cancelToken.Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(2));

}

}, cancelToken.Token, TaskCreationOptions.LongRunning);

task.Start();

}

}


其他代碼并不重要,重點在當鬧鈴時間到了后要執(zhí)行ItIsTimeToAlarm(); 我們在這里發(fā)出事件以便通知觀察者,.net中實現(xiàn)event模型有三要素,


1.為主題(subject)要定義一個event, public event Action Alarm;


2.為主題(subject)的信息定義一個EventArgs,即AlarmEventArgs,這里面包含了事件所有的信息


3.主題(subject)通過以下方式發(fā)出事件


var args = new AlarmEventArgs(_alarmTime.Value, 0.92m);

OnAlarmEvent(args);


OnAlarmEvent方法的定義


public virtual void OnAlarm(AlarmEventArgs e)

{

if(Alarm!=null)

Alarm(this,e);

}


這里要注意命名規(guī)范,事件內(nèi)容-AlarmEventArgs,事件-Alarm(動詞,例如KeyPress),觸發(fā)事件的方法 void OnAlarm(),這些命名都要符合事件模型的命名規(guī)范。

智能鬧鐘(SmartClock)已經(jīng)實現(xiàn)完畢,我們在牛奶加熱器(MilkSchedule)中訂閱這個Alarm消息:


public void PrepareMilkInTheMorning()

{

_clock.Alarm += (clock, args) =>

{

Message ='Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%'.FormatWith(args.AlarmTime, args.ElectricQuantity*100);

Console.WriteLine(Message);

};

_clock.SetAlarmTime(TimeSpan.FromSeconds(2));

}


在面包烘烤機中同樣可以用_clock.Alarm+=(clock,args)=>{//it is time to roast bread}訂閱鬧鈴消息。


至此,event模型介紹完畢,實現(xiàn)過程還是有點繁瑣的,并且事件模型使用不當會有memory leak的問題,當觀察者(obsever)訂閱了一個生命周期較長的主題(該主題生命周期長于觀察者),該觀察者將不會被垃圾回收(因為還有引用指向主題),詳見Understanding and Avoiding Memory Leaks with Event Handlers and Event Aggregators,開發(fā)者需要顯示退訂該主題(-=)。


園子里老A也寫過一篇如何利用弱引用解決該問題的博客:如何解決事件導致的Memory Leak問題:Weak Event Handlers。


二、利用.net中IObservable和IObserver實現(xiàn)觀察者模式


IObservable 顧名思義-可觀察的事物,即主題(subject),Observer很明顯就是觀察者了。


在我們的場景中智能鬧鐘是IObservable,該接口只定義了一個方法IDisposable Subscribe(IObserver observer);該方法命名讓人有點犯暈,Subscribe即訂閱的意思,不同于之前提到過的觀察者(observer)訂閱主題(subject)。在這里是主題(subject)來訂閱觀察者(observer),其實這里也說得通,因為在該模型下,主題(subject)維護了一個觀察者(observer)列表,所以有主題訂閱觀察者之說,我們來看鬧鐘的IDisposable Subscribe(IObserver observer)實現(xiàn):


public IDisposable Subscribe(IObserver observer)

{

if (!_observers.Contains(observer))

{

_observers.Add(observer);

}

return new DisposedAction(() => _observers.Remove(observer));

}


可以看到這里維護了一個觀察者列表_observers,鬧鐘在到點了之后會遍歷所有觀察者列表將消息逐一通知給觀察者


public override void ItIsTimeToAlarm()

{

var alarm = new AlarmData(_alarmTime.Value, 0.92m);

_observers.ForEach(o=>o.OnNext(alarm));

}


很明顯,觀察者有個OnNext方法,方法簽名是一個AlarmData,代表了要通知的消息數(shù)據(jù),接下來看看牛奶加熱器的實現(xiàn),牛奶加熱器作為觀察者(observer)當然要實現(xiàn)IObserver接口


public void Subscribe(TimeSpan timeSpan)

{

_unSubscriber = _clock.Subscribe(this);

_clock.SetAlarmTime(timeSpan);

}

public void Unsubscribe()

{

_unSubscriber.Dispose();

}

public void OnNext(AlarmData value)

{

Message ='Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%'.FormatWith(value.AlarmTime, value.ElectricQuantity * 100);

Console.WriteLine(Message);

}


除此之外為了方便使用面包烘烤器,我們還加了兩個方法Subscribe()和Unsubscribe(),看調(diào)用過程


var milkSchedule = new MilkSchedule();

//Act

milkSchedule.Subscribe(TimeSpan.FromSeconds(12));


三、Action函數(shù)式方案


在介紹該方案之前我需要說明,該方案并不是一個觀察者模型,但是它卻可以實現(xiàn)同樣的功能,并且使用起來更加簡練,也是我最喜歡的一種用法。


這種方案中,智能鬧鐘(smartClock)提供的API需要設計成這樣:


public void SetAlarmTime(TimeSpan timeSpan,Action alarmAction)

{

_alarmTime = _now().Add(timeSpan);

_alarmAction = alarmAction;

RunBackgourndRunner(_now, _alarmTime);

}


方法簽名中要接受一個Action,鬧鐘在到點后直接執(zhí)行該Action即可:


public override void ItIsTimeToAlarm()

{

if (_alarmAction != null)

{

var alarmData = new AlarmData(_alarmTime.Value, 0.92m);

_alarmAction(alarmData);

}

}


牛奶加熱器中使用這種API也很簡單:


_clock.SetAlarmTime(TimeSpan.FromSeconds(1), (data) =>

{

Message ='Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%'.FormatWith(data.AlarmTime, data.ElectricQuantity * 100);

});


在實際使用過程中我會把這種API設計成fluent api,調(diào)用起來代碼更清晰:


智能鬧鐘(smartClock)中的API:


public Clock SetAlarmTime(TimeSpan timeSpan)

{

_alarmTime = _now().Add(timeSpan);

RunBackgourndRunner(_now, _alarmTime);

return this;

}

public void OnAlarm(Action alarmAction)

{

_alarmAction = alarmAction;

}


牛奶加熱器中進行調(diào)用:


_clock.SetAlarmTime(TimeSpan.FromSeconds(2))

.OnAlarm((data) =>

{

Message ='Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%'.FormatWith(data.AlarmTime, data.ElectricQuantity * 100);

});


顯然改進后的寫法語義更好:鬧鐘.設置鬧鈴時間().當報警時(()=>{執(zhí)行以下功能})


這種函數(shù)式寫法更簡練,但是也有明顯的缺點,該模型不支持多個觀察者,當面包烘烤機使用這樣的API時,會覆蓋牛奶加熱器的函數(shù),即每次只支持一個觀察者使用。

本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Net設計模式實例之觀察者模式(Observer Pattern)
觀察者模式——氣象局高溫警告
【C#設計模式-觀察者模式】
設計模式隨筆系列:氣象站的故事
.NET設計模式(19):觀察者模式(Observer Pattern)
如何重構我們以前寫的垃圾代碼——觀察者模式
更多類似文章 >>
生活服務
熱點新聞
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服