WindowsをはじめとするGUIの普及によるものか、Observerパターンは数あるデザインパターンの中でも適用頻度の高いものの一つです。GUIアプリケーションではキー入力やボタンクリックといったイベントを受理したときの処理(イベントハンドラ)を定義し、イベントとイベントハンドラとの対応付けを行っておきます。そうすることでイベントに応じた処理、すなわち「イベントに反応する」ことができます。イベントとイベントハンドラとの対応付けに用いられているのがObserverパターンというわけ。
step-1: SubjectとObserver
Observerパターンでは2つのクラス、SubjectとObserverが関与します。Subjectはイベントの発行元、Observerがイベントに反応するハンドラの基底クラスとなります。Observerにはあらかじめ、イベントに反応して呼び出されるメソッド(ハンドラ)を宣言しておきます。またSubject内にはObserverへの参照(ポインタ)を保持するコンテナを内包させておきます。Subjectは内包するコンテナの各要素(Observerの参照)に対し、ハンドラを呼び出すことでイベント駆動を実現できます。ここまでは極めて単純なからくりです、さっくり実装してみましょう。
/* [step-1] * シンプルなObserverパターン実装 */ #include <vector> #include <algorithm> class Observer { friend class Subject; public: virtual ~Observer() {} protected: virtual void update(Subject*) =0; // コレが呼び出されるハンドラ }; class Subject { private: std::vector<Observer*> obs_; public: // Observerを登録 void add_observer(Observer* o) { if ( std::find(obs_.begin(), obs_.end(), o) == obs_.end() ) { obs_.push_back(o); } } // Observerの登録抹消 void delete_observer(Observer* o) { auto iter = std::find(obs_.begin(), obs_.end(), o); if ( iter != obs_.end() ) { obs_.erase(iter); } } protected: // 登録されたObserverのハンドラを呼び出す void notify() { std::for_each(obs_.begin(), obs_.end(), [this](Observer* o) { o->update(this);}); } }; /* * おためし */ #include <iostream> // 室温 class RoomTemp : public Subject { private: int value_; // public: RoomTemp(int initial) : value_(initial) {} void increment() // 室温を1度上げる { ++value_; notify(); } void decrement() // 室温を1度下げる { --value_; notify(); } int get_temp() const // 現在の室温 { return value_; } }; // 温度表示パネル : 室温の変化に応じて室温を表示する class TempDisp : public Observer { public: ~TempDisp() { std::cout << "お疲れ様でしたー" << std::endl; } protected: virtual void update(Subject* from) { int temp = static_cast<RoomTemp*>(from)->get_temp(); std::cout << "現在の温度: " << temp << "℃" << std::endl; } }; int main() { RoomTemp room(20); TempDisp disp; room.add_observer(&disp); room.increment(); room.increment(); room.increment(); room.decrement(); room.decrement(); room.decrement(); }
なんなく動いてくれます。が、一つ不便な点があります。例えばこんなコードではどうでしょう。
int main() { RoomTemp room(20); { TempDisp disp; room.add_observer(&disp); room.increment(); room.increment(); room.increment(); } // [*] room.decrement(); room.decrement(); room.decrement(); }
[*]に達した時点でObserverであるdispは寿命をまっとうし、この世から消えてなくなります。ところがSubjectであるroomはdispのポインタを保持したままになっています。従ってその後の room.decrement() によってこの世にいないdispのメソッドupdate()が呼ばれることになり、その結果は未定義です。
Observerがその役割を終えるときは、それに先立って必ず後始末(delete_observer)しなければならないのですが、std::shared_ptrを使って後始末のいらないObserverの実装を試みましょう。