観察者モードといえば、おそらく庭でたくさん見つけることができます。したがって、このブログを書く目的は 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) でこのアラーム メッセージをサブスクライブします。 > {// 焙煎の時間です
ブレッド}アラーム メッセージを購読します。
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)); }
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); }
这种方案中,智能闹钟(smartClock)提供的API需要设计成这样:
public void SetAlarmTime(TimeSpan timeSpan,Action<AlarmData> alarmAction) { _alarmTime = _now().Add(timeSpan); _alarmAction = alarmAction; RunBackgourndRunner(_now, _alarmTime); }
方法签名中要接受一个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模型,调用起来代码更清晰:
智能闹钟(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下的三种观察者模型实现方案,能在编程场景下选择最合适的模型当然是我们的最终目标。