ホームページ >バックエンド開発 >C#.Net チュートリアル >C# でオブザーバー パターンを実装する 3 つの方法

C# でオブザーバー パターンを実装する 3 つの方法

黄舟
黄舟オリジナル
2016-12-12 15:37:231400ブラウズ

観察者モードといえば、おそらく庭でたくさん見つけることができます。したがって、このブログを書く目的は 2 つあります:

1. オブザーバー パターンは、コード レベルに関係なく、パブリッシュ/サブスクライブ パターンを採用するために必要なパターンです。自分の理解に基づいて利用シナリオを再設計し、その中でオブザーバー パターンを柔軟に使用したい
2. C# でオブザーバー パターンを実装するための 3 つのソリューションをまとめたい

ここで、そのようなシナリオを想定し、オブザーバー モードを使用してニーズを実現しましょう:

将来、スマート ホームは各家庭に導入され、各家庭には顧客がカスタマイズして統合できる API が用意されているため、最初のスマート目覚まし時計 ( SmartClock) は、最初にこの目覚まし時計用の API セットを提供しており、アラーム時刻が設定されると、目覚まし時計がその時刻を通知します。この目覚まし時計のアラームメッセージは、飼い主のために牛乳、パン、歯磨き粉などを自動的に準備します。

このシナリオは非常に典型的なオブザーバー モードであり、スマート目覚まし時計の目覚まし時計が対象であり、ミルクウォーマー、パン焼き機、歯磨き粉絞り装置はこのトピックを実装するだけで済みます。疎結合コーディングモデル。 3 つのオプションを 1 つずつ使用して、この要件を実装してみましょう。

1. .net のイベント モデルを使用して実装します

.net のイベント モデルは、.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 でイベント モデルを実装するには、

1 というパブリック イベントのイベントを定義します。 Alarm;

2. サブジェクトの情報の EventArgs、つまりイベントのすべての情報を含む AlarmEventArgs を定義します

3. サブジェクトは次の方法でイベントを発行します

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) でこのアラーム メッセージをサブスクライブします。 > {// 焙煎の時間です ブレッド}アラーム メッセージを購読します。

この時点では、イベント モデルが導入されていますが、実装プロセスはまだ少し面倒で、イベント モデルを不適切に使用するとメモリが発生します。 リークの問題は、オブザーバーがライフ サイクルの長いトピック (トピックのライフ サイクルがオブザーバーよりも長い) をサブスクライブすると、オブザーバーがメモリ リサイクルされないことです (トピックへの参照がまだ存在するため)。「理解」を参照してください。詳細については、「イベント ハンドラーによるメモリ リークの回避」を参照してください。 Event Aggregator の場合、開発者はトピック (-=) を明示的に購読解除する必要があります。

庭の老人 A も、この問題を解決するために弱い参照を使用する方法に関するブログを書きました: イベントによって引き起こされるメモリ リーク問題を解決する方法: 弱いイベント ハンドラー。

2. .net で IObservable94e7e404026d3c15ea2ed68c9aab2a14 を使用する T>Observer パターンの実装

IObservable94e7e404026d3c15ea2ed68c9aab2a14 名前が示すように、観察可能なもの、つまりサブジェクトは、明らかにオブザーバーです。

このシナリオでは、スマート目覚まし時計は IObservable です。このインターフェイスは 1 つのメソッド IDisposable Subscribe(IObserver; このメソッドの名前は少し紛らわしいですが、Subscribe はサブスクリプションを意味し、前述のサブジェクト (subject) をサブスクライブするオブザーバー (observer) とは異なります。ここで、サブジェクトはオブザーバーをサブスクライブします。実際、このモデルではサブジェクトがオブザーバーのリストを維持するため、サブジェクトがオブザーバーをサブスクライブするという言い方があります。目覚まし時計の IDisposable を見てみましょう。 サブスクライブ (IObserver8742468051c85b06f0a0af9e3e506b5c オブザーバー) の実装:

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


ここでは、オブザーバー list_observers が維持されていることがわかります。目覚まし時計が時刻に達すると、すべてのオブザーバー リストを調べて、オブザーバーに 1 つずつ通知します。メッセージの

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

明らかに、オブザーバーには OnNext メソッドがあり、メソッド シグネチャは、通知されるメッセージ データを表す AlarmData です。 次に、オブザーバーとしてのミルク ヒーターの実装を見てみましょう。ヒーターはもちろん IObserver インターフェイスを実装する必要があります

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

さらに、パン焼き機の使用を容易にするために、Subscribe() と Unsubscribe() という 2 つのメソッドも追加しました。呼び出しプロセスを参照してください

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

3. アクション関数ソリューション

ソリューションを紹介する前に、このソリューションはオブザーバー モデルではないことを説明する必要がありますが、同じ機能を実現でき、より簡単に使用でき、これも私のお気に入りの用途の 1 つです。

这种方案中,智能闹钟(smartClock)提供的API需要设计成这样:

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

方法签名中要接受一个Action8742468051c85b06f0a0af9e3e506b5c,闹钟在到点后直接执行该Action8742468051c85b06f0a0af9e3e506b5c即可:

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下的三种观察者模型实现方案,能在编程场景下选择最合适的模型当然是我们的最终目标。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。