paint-brush
.NET C# の Observer デザイン パターンの基本ガイドby@ahmedtarekhasan
3,176
3,176

.NET C# の Observer デザイン パターンの基本ガイド

Ahmed Tarek Hasan19m2023/04/10
Read on Terminal Reader

この記事では、いくつかの拡張機能を備えた .NET C# の Observer デザイン パターンについて学習します。オブザーバー デザイン パターンを使用すると、サブスクライバーはプロバイダーに登録して、プロバイダーからの通知を受け取ることができます。プッシュベースの通知が必要なあらゆるシナリオに適しています。このパターンのユニークな点は、それを使用すると、密結合関係を持たなくてもこれを実現できることです。
featured image - .NET C# の Observer デザイン パターンの基本ガイド
Ahmed Tarek Hasan HackerNoon profile picture
0-item

この記事では、.NET C# のオブザーバー デザイン パターンといくつかの機能強化について説明します。


オブザーバーの設計パターンの定義

Observer デザイン パターンは、最も重要で一般的に使用されるデザイン パターンの 1 つです。


まず、 Observer Design Patternの正式な定義を確認しましょう。


通りマイクロソフトのドキュメント:


オブザーバー デザイン パターンを使用すると、サブスクライバーはプロバイダーに登録して、プロバイダーからの通知を受け取ることができます。プッシュベースの通知を必要とするあらゆるシナリオに適しています。このパターンは、プロバイダー (サブジェクトまたはオブザーバブルとも呼ばれます) と、ゼロ、1 つ、または複数のオブザーバーを定義します。オブザーバーはプロバイダーに登録され、定義済みの条件、イベント、または状態の変化が発生するたびに、プロバイダーはメソッドの 1 つを呼び出して、すべてのオブザーバーに自動的に通知します。このメソッド呼び出しでは、プロバイダーはオブザーバーに現在の状態情報を提供することもできます。 .NET では、オブザーバー デザイン パターンは、汎用のSystem.IObservable<T>およびSystem.IObserver<T>インターフェイスを実装することによって適用されます。ジェネリック型パラメーターは、通知情報を提供する型を表します。


したがって、上記の定義から、次のことが理解できます。

  1. 2 つのパーティまたはモジュールがあります。
  2. 提供する情報のストリームを持つモジュール。このモジュールは、 Provider (情報を提供するため)、 Subject (情報を外部に公開するため)、またはObservable (外部から監視できるため) と呼ばれます。
  3. 他の場所からの情報の流れに関心を持つモジュール。このモジュールはObserverと呼ばれます (情報を監視するため)。

UnsplashのDen Harrsonによる写真

オブザーバー デザイン パターンの利点

現在わかっているように、 Observer Design Pattern はObservableモジュールとObserverモジュールの間の関係を定式化します。オブザーバー デザイン パターンのユニークな点は、それを使用すると、密結合関係を持たなくてもこれを実現できることです。


パターンの仕組みを分析すると、次のことがわかります。

  1. Observable は、 Observerについて必要な最小限の情報を知っています。
  2. Observer は、 Observableについて必要な最小限の情報を知っています。
  3. 相互認識でさえ、具体的な実装ではなく、抽象化によって達成されます。
  4. 最後に、両方のモジュールがそれぞれの仕事を行うことができ、それぞれの仕事だけを行うことができます。

UnsplashのLucas Santosによる写真

使用される抽象化

これらは、 .NET C#Observer デザイン パターンを実装するために使用される抽象化です。



IObservable<out T>

これは、任意のObservableを表すCovariantインターフェースです。 .NET の Variance について詳しく知りたい場合は、こちらの記事をご覧ください。 .NET C# の共分散と反分散.


このインターフェイスで定義されているメンバーは次のとおりです。


 public IDisposable Subscribe (IObserver<out T> observer);


Subscribeメソッドを呼び出して、 Observer がその情報ストリームに関心を持っていることをObservableに通知する必要があります。


Subscribeメソッドは、 IDisposableインターフェイスを実装するオブジェクトを返します。その後、このオブジェクトをObserverが使用して、 Observableによって提供される情報のストリームからサブスクライブを解除できます。これが完了すると、オブザーバーは情報のストリームに対する更新について通知されなくなります。



IObserver<in T>

これは、任意のObserverを表すContravariantインターフェースです。 .NET の Variance について詳しく知りたい場合は、こちらの記事をご覧ください。 .NET C# の共分散と反分散.


このインターフェイスで定義されているメンバーは次のとおりです。


 public void OnCompleted (); public void OnError (Exception error); public void OnNext (T value);


OnCompletedメソッドは、 Observableによって呼び出され、情報のストリームが完了し、 Observer がそれ以上の情報を期待しないことをObserverに通知する必要があります。


OnErrorメソッドは、 Observableによって呼び出され、 Observerにエラーが発生したことを通知する必要があります。


OnNextメソッドは、 Observableによって呼び出され、新しい情報の準備ができてストリームに追加されていることをObserverに通知する必要があります。


UnsplashのTadasSarによる写真

マイクロソフトの実装

それでは、 Microsoft がObserver デザイン パターンをC# で実装することをどのように推奨しているかを見てみましょう。後で、私が自分で実装したマイナーな拡張機能をいくつか紹介します。


簡単な天気予報コンソール アプリケーションを作成します。このアプリケーションには、 WeatherForecastモジュール (Observable、Provider、Subject) とWeatherForecastObserverモジュール (Observer) があります。


それでは、実装の検討を始めましょう。



ウェザーインフォ

namespace Observable { public class WeatherInfo { internal WeatherInfo(double temperature) { Temperature = temperature; } public double Temperature { get; } } }


これは、情報ストリームに流れる情報を表すエンティティです。



天気予報

using System; using System.Collections.Generic; namespace Observable { public class WeatherForecast : IObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IDisposable Subscribe(IObserver<WeatherInfo> observer) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } return new WeatherForecastUnsubscriber(m_Observers, observer); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } }


ここで気付くこと:

  1. WeatherForecastクラスはIObservable<WeatherInfo>を実装しています。
  2. Subscribeメソッドの実装では、渡された Observer が以前に登録されていたかどうかを確認します。そうでない場合は、ローカルのm_Observersオブザーバー リストに追加します。次に、ローカルのm_WeatherInfoListリストにあるすべてのWeatherInfoエントリを 1 つずつループし、Observer のOnNextメソッドを呼び出して Observer に通知します。
  3. 最後に、情報ストリームからの登録解除のために Observer が使用するWeatherForecastUnsubscriberクラスの新しいインスタンスを返します。
  4. RegisterWeatherInfoメソッドは、メイン モジュールが新しいWeatherInfoを登録できるように定義されています。現実の世界では、これは内部のスケジュールされたAPI 呼び出し、またはSignalR Hubへのリスナー、または情報源として機能するその他のものに置き換えることができます。



Unsubscriber<T>

 using System; using System.Collections.Generic; namespace Observable { public class Unsubscriber<T> : IDisposable { private readonly List<IObserver<T>> m_Observers; private readonly IObserver<T> m_Observer; private bool m_IsDisposed; public Unsubscriber(List<IObserver<T>> observers, IObserver<T> observer) { m_Observers = observers; m_Observer = observer; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing && m_Observers.Contains(m_Observer)) { m_Observers.Remove(m_Observer); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } }


ここで気付くこと:

  1. これは、Un-subscriber の基本クラスです。
  2. Disposable Design Patternを適用してIDisposableを実装します。
  3. コンストラクターを介して、オブザーバーの完全なリストと、それが作成されたオブザーバーを受け取ります。
  4. 廃棄中に、オブザーバーがオブザーバーの完全なリストに既に存在するかどうかを確認します。はいの場合、リストから削除します。



天気予報購読解除

using System; using System.Collections.Generic; namespace Observable { public class WeatherForecastUnsubscriber : Unsubscriber<WeatherInfo> { public WeatherForecastUnsubscriber( List<IObserver<WeatherInfo>> observers, IObserver<WeatherInfo> observer) : base(observers, observer) { } } }


ここで気付くこと:

  1. これはUnsubscriber<T>クラスから継承しています。
  2. 特別な取り扱いはありません。



WeatherForecastObserver

 using System; namespace Observable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } }


ここで気付くこと:

  1. WeatherForecastObserverクラスはIObserver<WeatherInfo>を実装しています。
  2. OnNextメソッドでは、温度をコンソールに書き込みます。
  3. OnCompletedメソッドでは、「完了」をコンソールに書き込みます。
  4. OnErrorメソッドでは、「エラー」をコンソールに書き込みます。
  5. メインモジュールが登録プロセスをトリガーできるように、 void Subscribe(WeatherForecast provider)メソッドを定義しました。返されたサブスクライバー解除オブジェクトは、サブスクライブ解除の場合に使用するために内部的に保存されます。
  6. 同じ概念を使用して、 void Unsubscribe()メソッドが定義され、内部的に保存されたサブスクライバー解除オブジェクトを利用します。



プログラム

using System; namespace Observable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } }


ここで気付くこと:

  1. プロバイダーのインスタンスを作成しました。
  2. 次に、3つの情報を登録しました。
  3. この時点まで、オブザーバーが定義されていないため、コンソールには何も記録されません。
  4. 次に、オブザーバーのインスタンスを作成しました。
  5. 次に、オブザーバーをストリームにサブスクライブしました。
  6. この時点で、コンソールに記録された 3 つの温度が表示されます。これは、オブザーバーがサブスクライブすると、既存の情報について通知されるためです。私たちの場合、それらは 3 つの情報です。
  7. 次に、2 つの情報を登録します。
  8. そのため、さらに 2 つのメッセージがコンソールに記録されます。
  9. 次に、購読を解除します。
  10. 次に、1 つの情報を登録します。
  11. ただし、オブザーバーが既にサブスクライブを解除しているため、この情報はコンソールに記録されません。
  12. その後、オブザーバーは再びサブスクライブします。
  13. 次に、1 つの情報を登録します。
  14. したがって、この情報はコンソールに記録されます。


最後に、これを実行すると、次の結果になります。


アーメド・タレクによる画像


UnsplashのBrunoYamazakyによる写真

私の拡張実装

Microsoft の実装を確認したところ、いくつかの懸念事項が見つかりました。そのため、いくつかの小さな変更を行うことにしました。


IExtendedObservable<out T>

 using System; using System.Collections.Generic; namespace ExtendedObservable { public interface IExtendedObservable<out T> : IObservable<T> { IReadOnlyCollection<T> Snapshot { get; } IDisposable Subscribe(IObserver<T> observer, bool withHistory); } }


ここで気付くこと:

  1. IExtendedObservable<out T>インターフェイスは、 IObservable<T>インターフェイスを拡張します。
  2. 共変です。詳しく知りたい方はこちらの記事もチェック.NET C# の共分散と反分散.
  3. IReadOnlyCollection<T> Snapshotプロパティを定義して、他のモジュールがサブスクライブしなくても既存の情報エントリのインスタント リストを取得できるようにしました。
  4. また、追加のbool withHistoryパラメーターを使用してIDisposable Subscribe(IObserver<T> observer, bool withHistory)メソッドを定義し、オブザーバーがサブスクライブ時に既存の情報エントリについて通知を受けるかどうかを決定できるようにしました。



退会者

using System; namespace ExtendedObservable { public class Unsubscriber : IDisposable { private readonly Action m_UnsubscribeAction; private bool m_IsDisposed; public Unsubscriber(Action unsubscribeAction) { m_UnsubscribeAction = unsubscribeAction; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing) { m_UnsubscribeAction(); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } }


ここで気付くこと:

  1. 現在、 Unsubscriberクラスはジェネリックではありません。
  2. これは、情報エンティティの型を知る必要がなくなったためです。
  3. オブザーバーの完全なリストとそれが作成されたオブザーバーにアクセスする代わりに、オブザーバブルが破棄されたときにオブザーバブルに通知し、オブザーバブルが登録解除プロセスを単独で処理します。
  4. このように、それは以前よりも少なくなり、その仕事をしているだけです.



天気予報購読解除

using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecastUnsubscriber : Unsubscriber { public WeatherForecastUnsubscriber( Action unsubscribeAction) : base(unsubscribeAction) { } } }


ここで気付くこと:

  1. Unsubscriber<T>から<T>部分を削除しました。
  2. そして今、コンストラクターは、破棄の場合に呼び出されるActionを受け取ります。



天気予報

using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecast : IExtendedObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IReadOnlyCollection<WeatherInfo> Snapshot => m_WeatherInfoList; public IDisposable Subscribe(IObserver<WeatherInfo> observer) { return Subscribe(observer, false); } public IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); if (withHistory) { foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } } return new WeatherForecastUnsubscriber( () => { if (m_Observers.Contains(observer)) { m_Observers.Remove(observer); } }); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } }


ここで気付くこと:

  1. 内部のm_WeatherInfoListリストを返すIReadOnlyCollection<WeatherInfo> Snapshotプロパティ以外はほとんど同じですが、 IReadOnlyCollectionと同じです。
  2. そしてwithHistoryパラメータを利用するIDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory)メソッド。



WeatherForecastObserver

 using System; namespace ExtendedObservable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this, true); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } }


ここで気付くのは、 Subscribe(WeatherForecast provider)を除いてほぼ同じであり、履歴を使用してSubscribeするかどうかを決定することです。



プログラム

using System; namespace ExtendedObservable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } }


以前と同じです。




最後に、これを実行すると、以前と同じ結果になるはずです。


アーメド・タレクによる画像


UnsplashのEmily Morterによる写真、Ahmed Tarekによる調整

次は何ですか

これで、.NET C# のオブザーバー デザイン パターンの基本を理解できました。しかし、これで話は終わりではありません。


IObservable<T>およびIObserver<T>インターフェイスの上に構築されたライブラリがあり、便利な機能を提供します。


これらのライブラリの 1 つは、 .NET (Rx) のリアクティブ拡張機能図書館。これは、非同期プログラミングをサポートする一連の拡張メソッドと LINQ 標準シーケンス演算子で構成されています。


したがって、これらのライブラリを調べて試してみることをお勧めします。私はあなたがそれらのいくつかを好きになると確信しています.


こちらにも掲載。