オブザーバーパターン
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 94/104。
Observerパターンは、オブジェクト間の1対多の依存関係を定義します。1つのオブジェクト(サブジェクト)の状態が変化すると、そのすべての依存関係にあるもの(オブザーバー)に自動的に通知されます。これは、イベントシステム、UIの更新、または複数のオブジェクトが変更に反応する必要があるあらゆるシナリオに最適です。
このパターンには、2つの主要な役割があります。オブザーバーのリストを保持して通知を行うSubjectと、更新インターフェースを実装するObserversです:
#include <iostream>
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
std::vector<Observer*> observers;
int state = 0;
public:
void attach(Observer* obs) {
observers.push_back(obs);
}
void detach(Observer* obs) {
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void setState(int value) {
state = value;
notify();
}
void notify() {
for (Observer* obs : observers) {
obs->update(state);
}
}
};
class Display : public Observer {
std::string name;
public:
Display(const std::string& n) : name(n) {}
void update(int value) override {
std::cout << name << " received: " << value << "\n";
}
};
int main() {
Subject sensor;
Display screen1("Screen1"), screen2("Screen2");
sensor.attach(&screen1);
sensor.attach(&screen2);
sensor.setState(42); // 両方のディスプレイに通知される
}setState() が呼び出されると、サブジェクトは登録されているすべてのオブザーバーを反復処理し、それらの update() メソッドを呼び出します。オブザーバーはいつでもアタッチまたはデタッチできるため、システムは柔軟で疎結合になります。
あるオブジェクトへの変更が他のオブジェクトの更新を必要とし、かつそれらのオブジェクトを密結合させたくない場合に、Observerパターンを使用します。
チャレンジ
簡単Observer パターンを使用して、気象観測所 (Weather Station) モニタリングシステムを構築しましょう。温度データを追跡し、温度が変化するたびに複数の表示ユニットに自動的に通知する気象観測所を作成します。これは、このパターンの典型的な実世界での応用例です。
コードは以下の 3 つのファイルに分けて構成します:
Observer.h: オブザーバーインターフェースと具体的な表示オブザーバーを定義します。純粋仮想関数
update(double temperature)を持つ抽象クラスObserverを作成します。オブザーバーはこのメソッドを実装して通知を受け取ります。また、仮想デストラクタも定義してください。次に、
Observerを継承するTemperatureDisplayクラスを作成します。各表示ユニットは、コンストラクタを通じて設定される名前 (string) を持ちます。update()が呼び出されたとき、次のように出力する必要があります:[DisplayName]: Temperature is [temperature] degreesWeatherStation.h: 温度を保持し、オブザーバーに通知するサブジェクトを作成します。WeatherStationクラスは以下の機能を持つ必要があります:- オブザーバーのポインタのリストと現在の温度(
0.0で初期化)を保持する - オブザーバーを登録するための
attach(Observer* obs)メソッドを持つ - オブザーバーを削除するための
detach(Observer* obs)メソッドを持つ - 温度を更新し、登録されているすべてのオブザーバーに通知する
setTemperature(double temp)メソッドを持つ - 各オブザーバーの
update()を現在の温度で呼び出すプライベートなnotify()メソッドを持つ
- オブザーバーのポインタのリストと現在の温度(
main.cpp: Observer パターンの動作を実演します。以下の 3 つの入力を読み取ります:
- 1 つ目の表示ユニットの名前 (string)
- 2 つ目の表示ユニットの名前 (string)
- 温度の値 (double)
WeatherStationと、指定された名前を持つ 2 つのTemperatureDisplayオブジェクトを作成します。両方の表示ユニットをステーションにアタッチし、温度を入力値に設定します。両方の表示ユニットが自動的に更新を受け取り、メッセージを出力する必要があります。その後、1 つ目の表示ユニットをデタッチし、入力値より 5 度高い新しい温度を設定します。この更新は 2 つ目の表示ユニットだけが受け取る必要があります。
例えば、入力が Kitchen、Bedroom、22.5 の場合:
Kitchen: Temperature is 22.5 degrees
Bedroom: Temperature is 22.5 degrees
Bedroom: Temperature is 27.5 degrees入力が Office、Lobby、18.0 の場合:
Office: Temperature is 18 degrees
Lobby: Temperature is 18 degrees
Lobby: Temperature is 23 degreesObserver パターンがいかに疎結合なシステムを実現しているかに注目してください。気象観測所は特定の表示ユニットについて何も知る必要はなく、単にリッスンしている対象に通知するだけです。オブザーバーをデタッチすると、自動的に更新の受信が停止します。
チートシート
Observer パターンは、サブジェクトの状態が変化したときに、すべてのオブザーバーに自動的に通知が行くような、1 対多の依存関係を定義します。
このパターンには 2 つの主要な役割があります。
- Subject: オブザーバーのリストを保持し、状態の変化を通知します
- Observer: 通知を受け取るための更新インターフェースを実装します
基本的な実装例:
#include <iostream>
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
std::vector<Observer*> observers;
int state = 0;
public:
void attach(Observer* obs) {
observers.push_back(obs);
}
void detach(Observer* obs) {
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void setState(int value) {
state = value;
notify();
}
void notify() {
for (Observer* obs : observers) {
obs->update(state);
}
}
};
class Display : public Observer {
std::string name;
public:
Display(const std::string& n) : name(n) {}
void update(int value) override {
std::cout << name << " received: " << value << "\n";
}
};主要なメソッド:
attach(): 通知を受け取るオブザーバーを登録しますdetach(): 通知リストからオブザーバーを削除しますnotify(): すべてのオブザーバーをループし、それぞれのupdate()メソッドを呼び出します
1 つのオブジェクトへの変更が、それらの間の密結合を避けつつ、他のオブジェクトの更新を必要とする場合に Observer パターンを使用します。
自分で試してみよう
#include <iostream>
#include <string>
#include "WeatherStation.h"
using namespace std;
int main() {
// 入力を読み込む
string display1Name, display2Name;
double temperature;
cin >> display1Name;
cin >> display2Name;
cin >> temperature;
// TODO: WeatherStation オブジェクトを作成する
// TODO: 入力された名前を使用して2つの TemperatureDisplay オブジェクトを作成する
// TODO: 両方のディスプレイをウェザーステーションにアタッチする
// TODO: 温度を入力値に設定する
// (両方のディスプレイがメッセージを表示する必要があります)
// TODO: 1つ目のディスプレイをデタッチする
// TODO: 温度を入力値より5度高く設定する
// (2つ目のディスプレイのみが表示される必要があります)
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス