Observerパターン (イベント)
CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 54/70。
オブザーバーパターンは、1つのオブジェクト(サブジェクト)がその状態の変化を複数の依存オブジェクト(オブザーバー)に通知する、1対多の関係を構築します。C#では、イベントによってこのパターンをエレガントに実装するための組み込みの方法が提供されています。
このパターンには、イベントを発生させるパブリッシャーと、それらに応答するサブスクライバーが含まれます。以下は、株価モニターの実用的な例です:
public class Stock
{
private decimal _price;
public event Action<decimal> PriceChanged;
public decimal Price
{
get => _price;
set
{
_price = value;
PriceChanged?.Invoke(_price); // すべてのオブザーバーに通知する
}
}
}
public class PriceAlert
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Alert: Price is now {newPrice}");
}
}
public class PriceLogger
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Logged: {newPrice}");
}
}サブスクライバーは += を使用してメソッドをイベントにアタッチし、-= を使用してデタッチできます:
var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();
stock.PriceChanged += alert.OnPriceChanged;
stock.PriceChanged += logger.OnPriceChanged;
stock.Price = 150.50m;
// 出力:
// アラート: 価格が 150.50 になりました
// ログ: 150.50
stock.PriceChanged -= logger.OnPriceChanged; // 購読解除
stock.Price = 155.00m;
// 出力: アラート: 価格が 155.00 になりましたObserverパターンは疎結合を促進します。Stockクラスは、そのオブザーバーについて何も知る必要がありません。パブリッシャーを修正することなく新しいオブザーバーを購読させることができるため、システムの拡張やメンテナンスが容易になります。
チャレンジ
簡単イベントを使用したオブザーバーパターンを用いて、温度監視システムを構築しましょう。温度が変化するたびに複数のオブザーバーに通知するセンサーを作成します。これは、さまざまなコンポーネントが温度の更新に反応する必要がある空調制御システムのようなシナリオに最適です。
コードは3つのファイルに分けて構成します:
TemperatureSensor.cs:Monitoring名前空間にTemperatureSensorクラスを作成します。これはパブリッシャー、つまり他のオブジェクトが監視する対象となります。センサーには以下のものを含めてください:- 現在の温度を保存するためのプライベートフィールド
- 新しい温度をサブスクライバーに通知するために
Action<double>を使用するTemperatureChangedという名前のイベント - 温度が新しい値に設定されるたびにイベントを発生させる
Temperatureプロパティ
Observers.cs: 温度変化をサブスクライブする2つのオブザーバークラスを同じ名前空間に作成します:DisplayUnit-Display: {temp}Cと出力するOnTemperatureChanged(double temp)メソッドを持ちますAlarmSystem-Alarm Check: {temp}Cと出力するOnTemperatureChanged(double temp)メソッドを持ちます
Program.cs: センサーと両方のオブザーバーを作成して、すべてをまとめます。両方のオブザーバーをセンサーのイベントにサブスクライブさせ、温度を更新します。その後、アラームシステムのサブスクライブを解除し、再び温度を更新して、ディスプレイユニットだけが通知を受け取ることを確認します。
2つの入力を受け取ります:
- 1つ目の温度の読み取り値(例:
22.5) - 2つ目の温度の読み取り値(例:
28.0)
プログラムは以下の手順を実行する必要があります:
- センサーと両方のオブザーバーを作成する
- 両方のオブザーバーを
TemperatureChangedイベントにサブスクライブさせる - センサーの温度を1つ目の入力値に設定する(両方のオブザーバーに通知される必要があります)
AlarmSystemのイベントへのサブスクライブを解除する- センサーの温度を2つ目の入力値に設定する(ディスプレイのみに通知される必要があります)
例えば、入力が 22.5 と 28.0 の場合、出力は以下のようになります:
Display: 22.5C
Alarm Check: 22.5C
Display: 28Cこれはオブザーバーパターンの核心的な利点を示しています。センサーは何が自分を監視しているかを知る必要も、気にする必要もありません。オブザーバーは動的にサブスクライブや解除ができ、パブリッシャーは単にその時点で監視している対象に通知を送るだけです。
チートシート
Observer パターンは、あるオブジェクト(サブジェクト)の状態が変化したときに、複数の依存オブジェクト(オブザーバー)に通知する、1対多の関係を構築します。
C# では、イベントがこのパターンを実装するための組み込みの方法を提供します。このパターンには、イベントを発生させるパブリッシャーと、それに応答するサブスクライバーが含まれます。
基本的なイベントの実装
event Action<T> を使用してイベントを定義し、状態が変化したときにそれを呼び出します。
public class Stock
{
private decimal _price;
public event Action<decimal> PriceChanged;
public decimal Price
{
get => _price;
set
{
_price = value;
PriceChanged?.Invoke(_price); // すべてのオブザーバーに通知
}
}
}オブザーバーの作成
オブザーバーは、イベントのシグネチャに一致するメソッドを持つクラスです。
public class PriceAlert
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Alert: Price is now {newPrice}");
}
}
public class PriceLogger
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Logged: {newPrice}");
}
}購読と購読解除
イベントを購読するには += を、購読を解除するには -= を使用します。
var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();
stock.PriceChanged += alert.OnPriceChanged; // 購読
stock.PriceChanged += logger.OnPriceChanged; // 購読
stock.Price = 150.50m; // 両方のオブザーバーに通知される
stock.PriceChanged -= logger.OnPriceChanged; // 購読解除
stock.Price = 155.00m; // アラートのみに通知されるObserver パターンは疎結合を促進します。パブリッシャーはオブザーバーについて何も知る必要はありません。パブリッシャーを修正することなく、新しいオブザーバーが購読を追加できます。
自分で試してみよう
using System;
using Monitoring;
class Program
{
public static void Main(string[] args)
{
// 入力温度を読み込む
double temp1 = Convert.ToDouble(Console.ReadLine());
double temp2 = Convert.ToDouble(Console.ReadLine());
// TODO: TemperatureSensor インスタンスを作成する
// TODO: DisplayUnit と AlarmSystem のインスタンスを作成する
// TODO: 両方のオブザーバーを TemperatureChanged イベントにサブスクライブ(登録)する
// TODO: センサーの温度を temp1 に設定する(両方のオブザーバーに通知される)
// TODO: AlarmSystem をイベントからアンサブスクライブ(解除)する
// TODO: センサーの温度を temp2 に設定する(ディスプレイのみに通知される)
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。