【デザインパターン】ObserverパターンをC++で実装

Observerパターン(オブザーバーパターン)は,デザインパターンの中でも特に重要でよく利用されるパターンです.

C++でObserverパターンを実装します.

目次

Observerパターンとは

観察者(Observerクラス)通知者(Subjectクラス)の二つのクラスで表現するパターンです.

Subjectの状態(変数等)が変化したことを観察者(Observer)は知ることが出来るため,それに応じて処理を行うという使い方をします.

観察対象(Subjectクラス)が変更されたときに,観察者(Observerクラス)に通知されます.

Observerパターンという名前から「観察」のように思われがちですが,実質的には「通知」が重要な役割を果たします

では,どのような場合,Observerパターンが適しているのかを考える必要があります...

デザインパターンとは,オブジェクト指向プログラムにおける重要な経験則です.
デザインパターン自体を学習することも重要ですが,
「オブジェクト指向の意義」や「なぜデザインパターンを使うのか」を理解することが最も重要です.

Observerパターンの使いどころ

オブザーバーパターンの使いどころ」は,「ある状態が変化したときにそれと同時になにかの処理をする場合」です.例えば,時間が変化した際に,そのログを取る場合に使用します.

オブザーバーパターンは,

ある状態の変化に対して同時に行う処理が複数ある(一対多)場合に,特に効果を発揮します.

逆に言えば,変化に対応した処理が一つ(一対一)の場合は処理が複雑になるだけであまり効果はありません.

オブザーバパターンはよく使われるのは下記のような場面です.

  1. GUIアプリケーション
    • ユーザー操作に応じて複数のウィジェットを自動的に更新
  2. データ監視
    • センサーデータや株価データなど、定期的に変化するデータの変化を通知
  3. イベント駆動システム
    • イベント発生時に、登録されたリスナーに通知してアクションを実行
  4. リアルタイム通知
    • メッセージングシステムやニュースフィードで新しい情報を登録者に自動的に通知
  5. ゲームの状態管理
    • プレイヤーの行動やゲーム状態の変化に応じて、ゲーム内の要素を自動的に更新

実装

C++で実装しました

抽象クラス

まずはオブザーバーパターンの抽象クラスです.


#include <algorithm>
#include <iostream>
#include <vector>

class Observer;
class Subject;

class Observer
{
public:
    Observer() {}
    virtual void Update(Subject &) = 0;

public:
    virtual ~Observer()                   = default;
    Observer(const Observer &)            = default;
    Observer &operator=(const Observer &) = default;
    Observer(Observer &&)                 = default;
    Observer &operator=(Observer &&)      = default;
};

class Subject
{
public:
    Subject() {}
    void Attach(Observer *obs)
    {
        obsrv_.emplace_back(obs);
    }
    void Detach(Observer *obs)
    {
        obsrv_.erase(std::remove(obsrv_.begin(), obsrv_.end(), obs));
    }
    void Notify()
    {
        for (const auto &o : obsrv_)
        {
            o->Update(*this);
        }
    }

private:
    std::vector<Observer *> obsrv_;

public:
    virtual ~Subject()                  = default;
    Subject(const Subject &)            = default;
    Subject &operator=(const Subject &) = default;
    Subject(Subject &&)                 = default;
    Subject &operator=(Subject &&)      = default;
};

具象クラス

次に,具象クラスを実装します.


class ConcreteSubject : public Subject
{
public:
    ConcreteSubject() {}
    void DoSomethingAndNotify()
    {
        /* do something */
        Subject::Notify();
    }

public:
    virtual ~ConcreteSubject()                          = default;
    ConcreteSubject(const ConcreteSubject &)            = default;
    ConcreteSubject &operator=(const ConcreteSubject &) = default;
    ConcreteSubject(ConcreteSubject &&)                 = default;
    ConcreteSubject &operator=(ConcreteSubject &&)      = default;
};

class ConcreteObserver : public Observer
{
public:
    ConcreteObserver() = delete;
    ConcreteObserver(ConcreteSubject *sub) : sub_(sub)
    {
        sub_->Attach(this);
    }
    void Update(Subject &sbj) override
    {
        if (this->sub_ == &sbj)
        {
            this->DoAfterNotify();
        }
    }
    void DoAfterNotify()
    {
        /* do something after notify by concrete subject */
    }

private:
    ConcreteSubject *sub_ = nullptr;

public:
    virtual ~ConcreteObserver()                           = default;
    ConcreteObserver(const ConcreteObserver &)            = default;
    ConcreteObserver &operator=(const ConcreteObserver &) = default;
    ConcreteObserver(ConcreteObserver &&)                 = default;
    ConcreteObserver &operator=(ConcreteObserver &&)      = default;
};

ConcreteObjectのコンストラクタで,ConcreteSubjectを登録します.

Observerクラスは,Subjectなしで実行することを想定しないため,次のように引数なしのデフォルトを削除しておくことをお勧めします.

ConcreteObserver() = delete;

ConcreteSubjectクラスは,Objectクラスがない状態でも動作します(動作するように実装すべきです).

その一方でConcreteObserverクラスはConcreteSubjectクラスが定義されていない場合は動作しません.そのため,次のように引数なしのデフォルトを削除しておくことをお勧めします.

ConcreteObserver() = delete;

利用方法

利用方法は,次のようになります.


int main(int argc, char const *argv[])
{
    ConcreteSubject sub;
    ConcreteObserver obs(&sub);

    obs.DoSomethingAndNotify();
    return 0;
}

例題

例題1

時刻の変化と同時に,コンソール画面に「更新されたことを出力」と「時刻を出力」します.

実装

#include <algorithm>
#include <iostream>
#include <vector>

class Observer;
class Subject;

class Observer
{
public:
    Observer() {}
    virtual void Update(Subject &) = 0;

public:
    virtual ~Observer()                   = default;
    Observer(const Observer &)            = default;
    Observer &operator=(const Observer &) = default;
    Observer(Observer &&)                 = default;
    Observer &operator=(Observer &&)      = default;
};

class Subject
{
public:
    Subject() {}
    void Attach(Observer *obs)
    {
        obsrv_.emplace_back(obs);
    }
    void Detach(Observer *obs)
    {
        obsrv_.erase(std::remove(obsrv_.begin(), obsrv_.end(), obs));
    }
    void Notify()
    {
        for (const auto &o : obsrv_)
        {
            o->Update(*this);
        }
    }

private:
    std::vector<Observer *> obsrv_;

public:
    virtual ~Subject()                  = default;
    Subject(const Subject &)            = default;
    Subject &operator=(const Subject &) = default;
    Subject(Subject &&)                 = default;
    Subject &operator=(Subject &&)      = default;
};

class ClockSecond : public Subject
{
public:
    ClockSecond() {}
    void Tick()
    {
        second_++;
        Subject::Notify();
    }
    unsigned long GetSecond()
    {
        return this->second_;
    }

private:
    unsigned long second_ = 0;

public:
    virtual ~ClockSecond()                      = default;
    ClockSecond(const ClockSecond &)            = default;
    ClockSecond &operator=(const ClockSecond &) = default;
    ClockSecond(ClockSecond &&)                 = default;
    ClockSecond &operator=(ClockSecond &&)      = default;
};

class DisplayUpdate : public Observer
{
public:
    DisplayUpdate() {}
    DisplayUpdate(ClockSecond *sub) : clock_(sub)
    {
        clock_->Attach(this);
    }
    void Update(Subject &sbj) override
    {
        if (this->clock_ == &sbj)
        {
            this->Dislay();
        }
    }
    void Dislay()
    {
        std::cout << "ClockSecond is Updated." << std::endl;
    }

private:
    ClockSecond *clock_ = nullptr;

public:
    virtual ~DisplayUpdate()                        = default;
    DisplayUpdate(const DisplayUpdate &)            = default;
    DisplayUpdate &operator=(const DisplayUpdate &) = default;
    DisplayUpdate(DisplayUpdate &&)                 = default;
    DisplayUpdate &operator=(DisplayUpdate &&)      = default;
};

class DisplaySecond : public Observer
{
public:
    DisplaySecond() {}
    DisplaySecond(ClockSecond *sub) : clock_(sub)
    {
        clock_->Attach(this);
    }
    void Update(Subject &sbj) override
    {
        if (this->clock_ == &sbj)
        {
            this->Dislay();
        }
    }
    void Dislay()
    {
        std::cout << "Updated: " << this->clock_->GetSecond() << " (sec)" << std::endl;
    }

private:
    ClockSecond *clock_ = nullptr;

public:
    virtual ~DisplaySecond()                        = default;
    DisplaySecond(const DisplaySecond &)            = default;
    DisplaySecond &operator=(const DisplaySecond &) = default;
    DisplaySecond(DisplaySecond &&)                 = default;
    DisplaySecond &operator=(DisplaySecond &&)      = default;
};

int main(int argc, char const *argv[])
{
    ClockSecond clock;
    DisplayUpdate disp_update(&clock);
    DisplaySecond disp_sec(&clock);

    clock.Tick();
    clock.Tick();
    clock.Tick();
    clock.Tick();
    return 0;
}

実行


./a.out 
ClockSecond is Updated.
Updated: 1 (sec)
ClockSecond is Updated.
Updated: 2 (sec)
ClockSecond is Updated.
Updated: 3 (sec)
ClockSecond is Updated.
Updated: 4 (sec)

オブザーバーは,登録した順に実行されます.

最後に

Observerパターンは,デザインパターンの中でも特によく使われる方法です.

リファクタリングを行う際は,積極的に利用しましょう.

>> リファクタリング(第2版): 既存のコードを安全に改善する

よかったらシェアしてね!
目次