亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C# > 正文

C#中觀察者模式的3種實現方式

2020-01-24 02:04:57
字體:
來源:轉載
供稿:網友

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

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

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

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

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

一、利用.net的Event模型來實現

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

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

復制代碼 代碼如下:

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

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

復制代碼 代碼如下:

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))
                        {
                            //鬧鈴時間到了
                            ItIsTimeToAlarm();
                            cancelToken.Cancel();
                        }
                        cancelToken.Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(2));
                    }
                }, cancelToken.Token, TaskCreationOptions.LongRunning);
                task.Start();
            }
        }

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

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

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

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

復制代碼 代碼如下:

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

OnAlarmEvent方法的定義

復制代碼 代碼如下:

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

這里要注意命名,事件內容-AlarmEventArgs,事件-Alarm(動詞,例如KeyPress),觸發事件的方法 void OnAlarm(),這些元素都要符合事件模型的命名規范。
智能鬧鐘(SmartClock)已經實現完畢,我們在牛奶加熱器(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模型介紹完畢,實現過程還是有點繁瑣的,并且事件模型使用不當會有memory leak的問題,當觀察者(obsever)訂閱了一個生命周期較長的主題(該主題生命周期長于觀察者),該觀察者并不會被內存回收(因為還有引用指主題),詳見Understanding and Avoiding Memory Leaks with Event Handlers and Event Aggregators,開發者需要顯示退訂該主題(-=)。

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

二、利用.net中IObservable<out T>和IObserver<in T>實現觀察者模式

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

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

復制代碼 代碼如下:

public IDisposable Subscribe(IObserver<AlarmData> 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,代表了要通知的消息數據,接下來看看牛奶加熱器的實現,牛奶加熱器作為觀察者(observer)當然要實現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(),看調用過程

復制代碼 代碼如下:

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

三、Action函數式方案

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

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

復制代碼 代碼如下:

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

方法簽名中要接受一個Action<T>,鬧鐘在到點后直接執行該Action<T>即可:

復制代碼 代碼如下:

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模型,調用起來代碼更清晰:

智能鬧鐘(smartClock)中的API:

復制代碼 代碼如下:

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

牛奶加熱器中進行調用:

復制代碼 代碼如下:

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

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

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

結束語,本文總結了.net下的三種觀察者模型實現方案,能在編程場景下選擇最合適的模型當然是我們的最終目標。本文提供下載本文章所使用的源碼,如需轉載請注明出處

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲裸体xxxx| 国产精品久久久久久久久久免费| 色狠狠av一区二区三区香蕉蜜桃| 欧美xxxx综合视频| 精品久久久免费| 日韩国产激情在线| 久久中文字幕视频| 91亚洲国产成人精品性色| 91久久久久久久久| 国产91精品网站| 亚洲福利影片在线| 亚洲第一网中文字幕| 亚洲丝袜一区在线| 国产精品美女久久久久久免费| 亚洲综合中文字幕68页| 欧美精品在线观看91| 国产成人一区二区三区电影| 91热福利电影| 97在线视频免费| 日韩在线视频国产| 日韩午夜在线视频| 青草热久免费精品视频| 日韩精品久久久久久久玫瑰园| 日本久久91av| 欧美激情综合亚洲一二区| 91成人天堂久久成人| 日韩国产高清视频在线| 色99之美女主播在线视频| 久久久久久国产精品三级玉女聊斋| 欧美一级片久久久久久久| 成人在线播放av| 欧洲精品毛片网站| 亚洲欧洲免费视频| 高清欧美电影在线| 欧美精品制服第一页| 亚洲精品视频网上网址在线观看| 91性高湖久久久久久久久_久久99| 岛国视频午夜一区免费在线观看| y97精品国产97久久久久久| 国产欧美 在线欧美| 午夜精品久久久久久久99热浪潮| 精品久久久久久久中文字幕| 国产精品视频久久| 日韩精品在线观看视频| 日韩久久精品电影| 久久久久久伊人| 亚洲视频在线视频| 国产成人精品综合| 136fldh精品导航福利| 91精品国产91久久久久| 日本视频久久久| 国产亚洲一区二区在线| 日韩av影视在线| 久久av在线播放| 国产亚洲欧美另类中文| 91精品视频免费| 日韩精品小视频| 亚洲精品久久久久中文字幕二区| 久久精品国产久精国产思思| www.亚洲一二| 久久精品国产99国产精品澳门| 日韩毛片在线观看| 亚洲嫩模很污视频| 国产婷婷97碰碰久久人人蜜臀| 欧美视频专区一二在线观看| 亚洲欧美国产视频| 久久精品久久久久久国产 免费| 欧美精品xxx| 欧美精品激情blacked18| 亚洲福利在线看| 久久久99免费视频| 国产精品美腿一区在线看| 亚洲第一偷拍网| 国产精品美女久久久久av超清| 国产精品盗摄久久久| 国产91在线播放精品91| 日韩成人黄色av| 黑人巨大精品欧美一区二区一视频| 亚洲人成电影在线观看天堂色| 久久99精品视频一区97| 中文字幕最新精品| 亚洲成人激情在线| 日韩美女视频免费在线观看| 国产日产欧美a一级在线| 欧美一性一乱一交一视频| 亚洲午夜未满十八勿入免费观看全集| 国产99视频在线观看| 日韩美女在线看| 国产在线精品成人一区二区三区| 91久久精品美女高潮| 一本色道久久综合狠狠躁篇的优点| 精品日韩美女的视频高清| 国产成人精品综合久久久| 国产美女高潮久久白浆| 亚洲va久久久噜噜噜久久天堂| 亚洲一区二区久久久久久| 亚洲精品在线视频| 狠狠色狠狠色综合日日小说| 国产精品日韩在线| 久久久国产精品x99av| 久久九九精品99国产精品| 菠萝蜜影院一区二区免费| 亚洲精品美女视频| 欧美日韩国产麻豆| 日韩日本欧美亚洲| 欧美日韩国产激情| 亚洲自拍另类欧美丝袜| 亚洲电影在线看| 欧美激情区在线播放| 国产一区二区动漫| 久久久精品免费| 日韩国产欧美精品一区二区三区| 欧美性猛交99久久久久99按摩| 亚洲第一av网| 亚洲free性xxxx护士白浆| 69国产精品成人在线播放| 欧美日韩在线视频首页| 亚洲午夜精品视频| 欧美成年人在线观看| 国模精品一区二区三区色天香| 精品亚洲一区二区三区四区五区| 久久人人爽人人爽人人片av高清| 欧美巨乳美女视频| 亚洲欧美激情在线视频| 欧美在线视频在线播放完整版免费观看| 亚洲精品动漫100p| 亚洲精品国偷自产在线99热| 国产日韩在线看| 5278欧美一区二区三区| 日韩电影免费在线观看中文字幕| 亚洲国产精品人人爽夜夜爽| 国产欧美在线视频| 国产高清在线不卡| 日韩男女性生活视频| 亚洲人成在线免费观看| 亚洲国产欧美一区二区三区久久| 亚洲一区第一页| 9.1国产丝袜在线观看| 最近2019好看的中文字幕免费| 欧美激情国产日韩精品一区18| 色综合天天狠天天透天天伊人| 在线观看中文字幕亚洲| 国产成人鲁鲁免费视频a| 亚洲男人的天堂在线播放| 国产精品99久久久久久久久| 亚洲日韩中文字幕在线播放| 久久综合五月天| 91在线精品播放| 久久韩剧网电视剧| 黄色一区二区在线观看| 热99精品里视频精品| 久久久伊人欧美| 国产精品黄色影片导航在线观看| 国产亚洲视频在线| 亚洲成年网站在线观看| 日韩电视剧免费观看网站| 欧美一级大片在线观看| 国产精品欧美亚洲777777| 92国产精品视频| 国产精品视频资源| 97久久精品国产| 日韩国产高清污视频在线观看| 中文字幕在线观看日韩| 欧美在线视频网站|