亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C#中觀察者模式的3種實(shí)現(xiàn)方式

 更新時(shí)間:2015年03月30日 11:42:47   投稿:junjie  
這篇文章主要介紹了C#中觀察者模式的3種實(shí)現(xiàn)方式,本文講解了利用.net的Event模型來(lái)實(shí)現(xiàn)、利用.net中IObservable<out T>和IObserver<in T>實(shí)現(xiàn)觀察者模式、Action函數(shù)式方案等3種方法,需要的朋友可以參考下

說(shuō)起觀察者模式,估計(jì)在園子里能搜出一堆來(lái)。所以寫這篇博客的目的有兩點(diǎn):

1.觀察者模式是寫松耦合代碼的必備模式,重要性不言而喻,拋開(kāi)代碼層面,許多組件都采用了Publish-Subscribe模式,所以我想按照自己的理解重新設(shè)計(jì)一個(gè)使用場(chǎng)景并把觀察者模式靈活使用在其中
2.我想把C#中實(shí)現(xiàn)觀察者模式的三個(gè)方案做一個(gè)總結(jié),目前還沒(méi)看到這樣的總結(jié)

現(xiàn)在我們來(lái)假設(shè)這樣的一個(gè)場(chǎng)景,并利用觀察者模式實(shí)現(xiàn)需求:

未來(lái)智能家居進(jìn)入了每家每戶,每個(gè)家居都留有API供客戶進(jìn)行自定義整合,所以第一個(gè)智能鬧鐘(smartClock)先登場(chǎng),廠家為此鬧鐘提供了一組API,當(dāng)設(shè)置一個(gè)鬧鈴時(shí)間后該鬧鐘會(huì)在此時(shí)做出通知,我們的智能牛奶加熱器,面包烘烤機(jī),擠牙膏設(shè)備都要訂閱此鬧鐘鬧鈴消息,自動(dòng)為主人準(zhǔn)備好牛奶,面包,牙膏等。

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

一、利用.net的Event模型來(lái)實(shí)現(xiàn)

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

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

復(fù)制代碼 代碼如下:

public void SetAlarmTime(TimeSpan timeSpan)
        {
            _alarmTime = _now().Add(timeSpan);
            RunBackgourndRunner(_now, _alarmTime);
        }

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

復(fù)制代碼 代碼如下:

protected void RunBackgourndRunner(Func<DateTime> now,DateTime? alarmTime )
        {
            if (alarmTime.HasValue)
            {
                var cancelToken = new CancellationTokenSource();
                var task = new Task(() =>
                {
                    while (!cancelToken.IsCancellationRequested)
                    {
                        if (now.AreEquals(alarmTime.Value))
                        {
                            //鬧鈴時(shí)間到了
                            ItIsTimeToAlarm();
                            cancelToken.Cancel();
                        }
                        cancelToken.Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(2));
                    }
                }, cancelToken.Token, TaskCreationOptions.LongRunning);
                task.Start();
            }
        }

其他代碼并不重要,重點(diǎn)在當(dāng)鬧鈴時(shí)間到了后要執(zhí)行ItIsTimeToAlarm(); 我們?cè)谶@里發(fā)出事件以便通知訂閱者,.net中實(shí)現(xiàn)event模型有三要素,

1.為主題(subject)要定義一個(gè)event, public event Action<Clock, AlarmEventArgs> Alarm;

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

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

復(fù)制代碼 代碼如下:

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

OnAlarmEvent方法的定義

復(fù)制代碼 代碼如下:

public virtual void OnAlarm(AlarmEventArgs e)
       {
           if(Alarm!=null)
               Alarm(this,e);
       }

這里要注意命名,事件內(nèi)容-AlarmEventArgs,事件-Alarm(動(dòng)詞,例如KeyPress),觸發(fā)事件的方法 void OnAlarm(),這些元素都要符合事件模型的命名規(guī)范。
智能鬧鐘(SmartClock)已經(jīng)實(shí)現(xiàn)完畢,我們?cè)谂D碳訜崞?MilkSchedule)中訂閱這個(gè)Alarm消息:
復(fù)制代碼 代碼如下:

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));
 
        }

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

至此,event模型介紹完畢,實(shí)現(xiàn)過(guò)程還是有點(diǎn)繁瑣的,并且事件模型使用不當(dāng)會(huì)有memory leak的問(wèn)題,當(dāng)觀察者(obsever)訂閱了一個(gè)生命周期較長(zhǎng)的主題(該主題生命周期長(zhǎng)于觀察者),該觀察者并不會(huì)被內(nèi)存回收(因?yàn)檫€有引用指主題),詳見(jiàn)Understanding and Avoiding Memory Leaks with Event Handlers and Event Aggregators,開(kāi)發(fā)者需要顯示退訂該主題(-=)。

園子里老A也寫過(guò)一篇如何利用弱引用解決該問(wèn)題的博客:如何解決事件導(dǎo)致的Memory Leak問(wèn)題:Weak Event Handlers

二、利用.net中IObservable<out T>和IObserver<in T>實(shí)現(xiàn)觀察者模式

IObservable<out T> 正如名稱含義-可觀察的事物,即主題(subject),Observer很明顯就是觀察者了。

在我們的場(chǎng)景中智能鬧鐘是IObservable,該接口只定義了一個(gè)方法IDisposable Subscribe(IObserver<T> observer);該方法命名讓人有點(diǎn)犯暈,Subscribe即訂閱的意思,不同于之前提到過(guò)的觀察者(observer)訂閱主題(subject)。在這里是主題(subject)來(lái)訂閱觀察者(observer),其實(shí)這里也說(shuō)得通,因?yàn)樵谠撃P拖?,主題(subject)維護(hù)了一個(gè)觀察者(observer)列表,所以有主題訂閱觀察者之說(shuō),我們來(lái)看鬧鐘的IDisposable Subscribe(IObserver<T> observer)實(shí)現(xiàn):

復(fù)制代碼 代碼如下:

public IDisposable Subscribe(IObserver<AlarmData> observer)
        {
            if (!_observers.Contains(observer))
            {
                _observers.Add(observer);
            }
            return new DisposedAction(() => _observers.Remove(observer));
        }

可以看到這里維護(hù)了一個(gè)觀察者列表_observers,鬧鐘在到點(diǎn)了之后會(huì)遍歷所有觀察者列表將消息逐一通知給觀察者

復(fù)制代碼 代碼如下:

public override void ItIsTimeToAlarm()
        {
            var alarm = new AlarmData(_alarmTime.Value, 0.92m);
            _observers.ForEach(o=>o.OnNext(alarm));
        }

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

復(fù)制代碼 代碼如下:

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);
       }

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

復(fù)制代碼 代碼如下:

var milkSchedule = new MilkSchedule();
//Act
milkSchedule.Subscribe(TimeSpan.FromSeconds(12));

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

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

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

復(fù)制代碼 代碼如下:

public void SetAlarmTime(TimeSpan timeSpan,Action<AlarmData> alarmAction)
       {
           _alarmTime = _now().Add(timeSpan);
           _alarmAction = alarmAction;
           RunBackgourndRunner(_now, _alarmTime);
       }

方法簽名中要接受一個(gè)Action<T>,鬧鐘在到點(diǎn)后直接執(zhí)行該Action<T>即可:

復(fù)制代碼 代碼如下:

public override void ItIsTimeToAlarm()
       {
           if (_alarmAction != null)
           {
               var alarmData = new AlarmData(_alarmTime.Value, 0.92m);
               _alarmAction(alarmData);   
           }
       }

牛奶加熱器中使用這種API也很簡(jiǎn)單:

復(fù)制代碼 代碼如下:

_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);
            });

在實(shí)際使用過(guò)程中我會(huì)把這種API設(shè)計(jì)成fluent模型,調(diào)用起來(lái)代碼更清晰:

智能鬧鐘(smartClock)中的API:

復(fù)制代碼 代碼如下:

public Clock SetAlarmTime(TimeSpan timeSpan)
        {
            _alarmTime = _now().Add(timeSpan);
            RunBackgourndRunner(_now, _alarmTime);
            return this;
        }
 
        public void OnAlarm(Action<AlarmData> alarmAction)
        {
            _alarmAction = alarmAction;
        }

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

復(fù)制代碼 代碼如下:

_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);
                });

顯然改進(jìn)后的寫法語(yǔ)義更好:鬧鐘.設(shè)置鬧鈴時(shí)間().當(dāng)報(bào)警時(shí)(()=>{執(zhí)行以下功能})

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

結(jié)束語(yǔ),本文總結(jié)了.net下的三種觀察者模型實(shí)現(xiàn)方案,能在編程場(chǎng)景下選擇最合適的模型當(dāng)然是我們的最終目標(biāo)。本文提供下載本文章所使用的源碼,如需轉(zhuǎn)載請(qǐng)注明出處

相關(guān)文章

  • C#各種數(shù)組的用法實(shí)例演示

    C#各種數(shù)組的用法實(shí)例演示

    這篇文章主要介紹了C#各種數(shù)組的用法,有助于初學(xué)者學(xué)習(xí)并鞏固C#關(guān)于數(shù)組的用法,需要的朋友可以參考下
    2014-07-07
  • C#中HttpWebRequest的用法詳解

    C#中HttpWebRequest的用法詳解

    這篇文章主要介紹了C#中HttpWebRequest的用法,以實(shí)例的形式詳細(xì)敘述了HttpWebRequest類中GET與POST的用法,非常具有參考借鑒價(jià)值,需要的朋友可以參考下
    2014-11-11
  • C#?VB.NET?實(shí)現(xiàn)在Word中嵌入多媒體(視頻、音頻)文件

    C#?VB.NET?實(shí)現(xiàn)在Word中嵌入多媒體(視頻、音頻)文件

    Word中可將Office、PDF、txt等文件作為OLE對(duì)象插入到文檔中,雙擊該對(duì)象可直接訪問(wèn)或編輯該文件,除了以上常見(jiàn)的文件格式對(duì)象,也可以插入多媒體文件,如視頻、音頻等。本篇文章介紹了通過(guò)C#實(shí)現(xiàn)在Word中插入多媒體文件。感興趣的可以學(xué)習(xí)一下
    2021-12-12
  • C# paddlerocrsharp識(shí)別身份證號(hào)的實(shí)現(xiàn)示例

    C# paddlerocrsharp識(shí)別身份證號(hào)的實(shí)現(xiàn)示例

    paddlerocrsharp可以進(jìn)行圖片識(shí)別,本文主要介紹了C# paddlerocrsharp識(shí)別身份證號(hào)的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02
  • C#使用Dictionary<string, string>拆分字符串與記錄log方法

    C#使用Dictionary<string, string>拆分字符串與記錄log方法

    這篇文章介紹了Dictionary<string, string>拆分字符串與記錄log的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-04-04
  • C#調(diào)用mmpeg進(jìn)行各種視頻轉(zhuǎn)換的類實(shí)例

    C#調(diào)用mmpeg進(jìn)行各種視頻轉(zhuǎn)換的類實(shí)例

    這篇文章主要介紹了C#調(diào)用mmpeg進(jìn)行各種視頻轉(zhuǎn)換的類,實(shí)例分析了C#調(diào)用mmpeg操作視頻文件的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-03-03
  • C#中Convert.ToInt32()和int.Parse()的區(qū)別介紹

    C#中Convert.ToInt32()和int.Parse()的區(qū)別介紹

    Convert是一個(gè)類,繼承自system.Object;int是值類型,在本文為大家詳細(xì)介紹下它與int.Parse()的區(qū)別,感興趣的朋友可以參考下
    2013-10-10
  • C#適配器模式的使用

    C#適配器模式的使用

    本文主要介紹了C#適配器模式的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • C# 正則判斷一個(gè)數(shù)字的格式是否有逗號(hào)的代碼

    C# 正則判斷一個(gè)數(shù)字的格式是否有逗號(hào)的代碼

    c#正則判斷一個(gè)格式化數(shù)字里是否有逗號(hào)的解決方法
    2008-07-07
  • C#命名空間與java包的區(qū)別分析

    C#命名空間與java包的區(qū)別分析

    這篇文章主要介紹了C#命名空間與java包的區(qū)別,較為詳細(xì)的分析了C#命名空間與java包的相同點(diǎn)與不同點(diǎn),非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-04-04

最新評(píng)論