Observerパターン (イベント)
CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 54/70。
Observerパターンは、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;
// 出力:
// Alert: Price is now 150.50
// Logged: 150.50
stock.PriceChanged -= logger.OnPriceChanged; // 購読解除
stock.Price = 155.00m;
// 出力: Alert: Price is now 155.00Observerパターンは疎結合を促進します。Stockクラスはオブザーバーについて何も知る必要がありません。パブリッシャーを変更しなくても新しいオブザーバーが登録できるため、システムを簡単に拡張し、保守できます。
チャレンジ
簡単イベントを使ったObserverパターンで、温度監視システムを作りましょう。温度が変化するたびに複数のオブザーバーに通知するセンサーを作成します。これは、さまざまなコンポーネントが温度の更新に反応する必要がある空調システムなどのシナリオに最適です。
コードを3つのファイルに分けます。
TemperatureSensor.cs:Monitoring名前空間にTemperatureSensorクラスを作成します。これはパブリッシャー、つまりほかのオブジェクトが監視する対象です。センサーには次の要素を持たせます。- 現在の温度を保存するプライベートフィールド
- 新しい温度を登録者に通知するために
Action<double>を使用する、TemperatureChangedというイベント - 温度が新しい値に設定されるたびにイベントを発生させる
Temperatureプロパティ
Observers.cs: 同じ名前空間に、温度変化を登録して受け取る2つのオブザーバークラスを作成します。DisplayUnit-OnTemperatureChanged(double temp)メソッドを持ち、Display: {temp}Cを出力します。AlarmSystem-OnTemperatureChanged(double temp)メソッドを持ち、Alarm Check: {temp}Cを出力します。
Program.cs: センサーと2つのオブザーバーを作成して、すべてを組み合わせます。両方のオブザーバーをセンサーのイベントに登録してから、温度を更新します。その後、アラームシステムの登録を解除し、もう一度温度を更新して、表示ユニットだけが通知を受け取ることを確認します。
入力は2つです。
- 1つ目の温度測定値(例:
22.5) - 2つ目の温度測定値(例:
28.0)
プログラムでは、次の処理を行います。
- センサーと2つのオブザーバーを作成する
- 両方のオブザーバーを
TemperatureChangedイベントに登録する - センサーの温度を1つ目の入力値に設定する(両方のオブザーバーに通知されます)
AlarmSystemのイベント登録を解除する- センサーの温度を2つ目の入力値に設定する(表示ユニットだけに通知されます)
たとえば、入力が22.5と28.0の場合、出力は次のようになります。
Display: 22.5C
Alarm Check: 22.5C
Display: 28Cこれによって、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 に設定する(ディスプレイにのみ通知される)
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: C#オンラインコンパイラ