14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C++】オブザーバーパターンでイベント駆動設計を!

Last updated at Posted at 2025-12-15

開発では「あるイベントが起きたら複数の処理を同時に呼び出す」場面がよくあります。
しかし、がむしゃらに記述を行うと密結合になってしまい、変更に弱いコードが生まれてしまいます。
そこでオブザーバーパターンを使用することで、疎結合を保ちつつ呼び出し処理を実現することができます。
本記事ではC++でのオブザーバーパターンの使用方法について紹介します。

前提知識

  • C++の基礎文法を理解している
  • オブジェクト指向について知っている

オブザーバーパターン/ObserverPatternとは

オブザーバーパターンObserver Pattern)とは、 「あるオブジェクトの状態変化を、依存している複数のオブジェクトに自動的に通知する仕組み」を提供するデザインパターンです。
コードの依存関係を減らせ、拡張性があるというメリットがあります。
その反面、乱用すると処理が重くなったり、優先順位を工夫する必要が出てきます。

オブザーバーパターンの構成と用語

オブザーバーパターンは以下のようなクラスで構成されています。

  • Subject/通知元
    • 状態を持ち、変化が起きたらObserverへ通知する
    • インターフェースとして作り、ConcreteSubjectに継承させる
  • Observer(監視者)
    • Subjectから通知を受け取り、処理を行う
    • インターフェースとして作り、ConcreteObserverに継承させる
  • ConcreteSubject(具体的通知元)
    • Subjectを実装したクラス
    • 状態を管理し、通知を実際に行う
  • ConcreteObserver(具体的監視者)
    • Observerを実装したクラス
    • 通知に応じて具体的な処理を行う

構造の図解

コード例

プレイヤーがダメージを受けたことを通知するコード

ISubject.h
//----------------------------------------------------------------------------
//! @file   ISubject.h
//! @brief  Subjectインターフェースの定義
//! @author つきの
//----------------------------------------------------------------------------
#pragma once
class IObserver;
#include <string>
//----------------------------------------------------------------------------
//! @class ISubject
//! @brief Subjectインターフェース
//! @note  ObserverパターンにおけるSubjectのインターフェース定義
//----------------------------------------------------------------------------
class ISubject {
public:
	//----------------------------------------------------------------------------
	//! @brief デストラクタ
	//----------------------------------------------------------------------------
	virtual ~ISubject() = default;

	//----------------------------------------------------------------------------
	//! @brief Observerを追加する関数
	//! @param observer [in] 追加するObserver
	//----------------------------------------------------------------------------
	virtual void addObserver(IObserver* observer) = 0;

	//----------------------------------------------------------------------------
	//! @brief Observerを削除する関数
	//! @param observer [in] 削除するObserver
	//----------------------------------------------------------------------------
	virtual void removeObserver(IObserver* observer) = 0;

	//----------------------------------------------------------------------------
	//! @brief 登録されているObserverに通知を送る関数
	//! @param event [in] イベント名
	//! @param value [in] イベントに関連する値
	//----------------------------------------------------------------------------
	virtual void notify(const std::string& event, int value) = 0;
};
IObserver.h
//----------------------------------------------------------------------------
//! @file	IObserver.h
//! @brief	Observerインターフェースの定義
//! @author つきの
//----------------------------------------------------------------------------
#pragma once
#include <string>
//----------------------------------------------------------------------------
//! @class IObserver
//! @brief Observerインターフェース
//! @note  ObserverパターンにおけるObserverのインターフェース定義
//----------------------------------------------------------------------------
class IObserver {
public:
	//----------------------------------------------------------------------------
	//! @brief デストラクタ
	//----------------------------------------------------------------------------
	virtual ~IObserver() = default;

	//----------------------------------------------------------------------------
	//! @brief 通知を受け取る処理
	//! @param event [in] イベント名
	//! @param value [in] イベントに関連する値
	//----------------------------------------------------------------------------
	virtual void onNotify(const std::string& event, int value) = 0;
};
Player.h
//----------------------------------------------------------------------------
//! @file	Player.h
//! @brief	Playerクラスの定義
//! @author つきの
//----------------------------------------------------------------------------
#pragma once
#include "ISubject.h"
#include <vector>
//----------------------------------------------------------------------------
//! @class Player
//! @brief プレイヤークラス
//! @note  ObserverパターンにおけるConcreteSubjectの役割
//----------------------------------------------------------------------------
class Player : public ISubject {
private:
	int hp;								// !< プレイヤーのHP
	std::vector<IObserver*> observers_;	// !< 登録されているObserverのリスト
public:
	//----------------------------------------------------------------------------
	// コンストラクタ
	//! @param hp [in] 初期HP
	//----------------------------------------------------------------------------
	Player(int hp);

	//----------------------------------------------------------------------------
	// Observerリストに追加を行う関数
	//! @param observer [in] 追加/削除するObserver
	//----------------------------------------------------------------------------
	void addObserver(IObserver* observer) override;

	//----------------------------------------------------------------------------
	// Observerリストから削除を行う関数
	//! @param observer [in] 追加/削除するObserver
	//----------------------------------------------------------------------------
	void removeObserver(IObserver* observer) override;

	//----------------------------------------------------------------------------
	// 登録されているObserverに通知を送る関数
	//! @param event [in] イベント名
	//! @param value [in] イベントに関連する値
	//----------------------------------------------------------------------------
	void notify(const std::string& event, int value) override;

	//----------------------------------------------------------------------------
	// ダメージを受ける関数
	//! @param damage [in] 受けるダメージ量
	//! @note  HPが変化した際にObserverに通知を送る
	//----------------------------------------------------------------------------
	void takeDamage(int damage);
};
Player.cpp
//----------------------------------------------------------------------------
//! @file	Player.cpp
//! @brief	Playerクラスの実装
//! @author つきの
//----------------------------------------------------------------------------
#include "Player.h"
#include "IObserver.h"
//----------------------------------------------------------------------------
//! @brief コンストラクタ
//----------------------------------------------------------------------------
Player::Player(int hp) 
	: hp(hp) {
}

//----------------------------------------------------------------------------
//! @brief Observerリストに追加を行う関数
//----------------------------------------------------------------------------
void Player::addObserver(IObserver* observer) {
	observers_.push_back(observer);
}

//----------------------------------------------------------------------------
//! @brief Observerリストから削除を行う関数
//----------------------------------------------------------------------------
void Player::removeObserver(IObserver* observer) {
	// 指定されたObserverをリストから削除
	observers_.erase(std::remove(observers_.begin(), observers_.end(), observer), observers_.end());
}

//----------------------------------------------------------------------------
//! @brief 登録されているObserverに通知を送る関数
//----------------------------------------------------------------------------
void Player::notify(const std::string& event, int value) {
	for (auto* obs : observers_) {
		obs->onNotify(event, value);	// 各Observerに通知を送る
	}
}

//----------------------------------------------------------------------------
//! @brief ダメージを受ける関数
//----------------------------------------------------------------------------
void Player::takeDamage(int damage) {
	hp -= damage;				// ダメージ分HPを減らす
	if (hp < 0) hp = 0;			// HPが0未満にならないようにする
	notify("HP_CHANGED", hp);	// HPが変化したことを通知
}
Logger.h
//----------------------------------------------------------------------------
//! @file	Logger.h
//! @brief	Loggerクラスの定義
//! @author つきの
//----------------------------------------------------------------------------
#pragma once
#include "IObserver.h"
//----------------------------------------------------------------------------
//! @class Logger
//! @brief ログ出力クラス
//! @note  ObserverパターンにおけるConcreteObserverの役割
//----------------------------------------------------------------------------
class Logger : public IObserver {
public:
	//----------------------------------------------------------------------------
	// 通知を受け取る処理
	//! @param event [in] イベント名
	//! @param value [in] イベントに関連する値
	//----------------------------------------------------------------------------
	void onNotify(const std::string& event, int value) override;
};
Logger.cpp
//----------------------------------------------------------------------------
//! @file	Logger.cpp
//! @brief	Loggerクラスの実装
//! @author つきの
//----------------------------------------------------------------------------
#include "Logger.h"
#include <iostream>
//----------------------------------------------------------------------------
//! @brief 通知を受け取る処理
//----------------------------------------------------------------------------
void Logger::onNotify(const std::string& event, int value) {
	std::cout << "[Log] Event=" << event << ", Value=" << value << std::endl;
}
SoundSystem.h
//----------------------------------------------------------------------------
//! @file	SoundSystem.h
//! @brief	SoundSystemクラスの定義
//! @author つきの
//----------------------------------------------------------------------------
#pragma once
#include "IObserver.h"
//----------------------------------------------------------------------------
//! @class SoundSystem
//! @brief サウンドシステムクラス
//! @note  ObserverパターンにおけるConcreteObserverの役割
//----------------------------------------------------------------------------
class SoundSystem : public IObserver {
public:
    //----------------------------------------------------------------------------
	// 通知を受け取る処理
	//! @param event [in] イベント名
	//! @param value [in] イベントに関連する値
    //----------------------------------------------------------------------------
    void onNotify(const std::string& event, int value) override;
};
SoundSystem.cpp
//----------------------------------------------------------------------------
//! @file	SoundSystem.cpp
//! @brief	SoundSystemクラスの実装
//! @author つきの
//----------------------------------------------------------------------------
#include "SoundSystem.h"
#include <iostream>
//----------------------------------------------------------------------------
//! @brief 通知を受け取る処理
//----------------------------------------------------------------------------
void SoundSystem::onNotify(const std::string& event, int value) {
	if (event == "HP_CHANGED") {
		std::cout << "[Sound] 再生: HP_CHANGED " << value << std::endl;
	}
}
main.cpp
//----------------------------------------------------------------------------
//! @file   main.cpp
//! @brief  オブザーバーパターンのサンプルコード
//! @author つきの
//----------------------------------------------------------------------------
#include "Player.h"
#include "Logger.h"
#include "SoundSystem.h"
#include <memory>
// エントリポイント
int main() {
	// Playerオブジェクトの作成
	std::unique_ptr<Player> player = std::make_unique<Player>(100);
	// Observerオブジェクトの作成
	std::unique_ptr<Logger> logger = std::make_unique<Logger>();
	std::unique_ptr<SoundSystem> sound_system = std::make_unique<SoundSystem>();
	// Observerの登録
	player->addObserver(logger.get());			// Loggerを登録
	player->addObserver(sound_system.get());	// SoundSystemを登録
	// ダメージを受けてみる
	player->takeDamage(20);
	// 終了
	return 0;
}
result
[Log] Event=HP_CHANGED, Value=80
[Sound] 再生: HP_CHANGED 80

通知を送れたようです。

クラス図

onNotify/イベントの通知を受け取る関数

Observerにイベントの通知を受け取って処理を行う関数を作ることで、イベント処理を可能としています。
IObserverに純粋仮想関数(本記事ではonNotify())を作成しておくことで、派生クラスで同名の関数があることを保証します。

IObserver.h
	//----------------------------------------------------------------------------
	//! @brief 通知を受け取る処理
	//! @param event [in] イベント名
	//! @param value [in] イベントに関連する値
	//----------------------------------------------------------------------------
	virtual void onNotify(const std::string& event, int value) = 0;
SoundSystem.cpp
//----------------------------------------------------------------------------
//! @brief 通知を受け取る処理
//----------------------------------------------------------------------------
void SoundSystem::onNotify(const std::string& event, int value) {
	if (event == "HP_CHANGED") {
		std::cout << "[Sound] 再生: HP_CHANGED " << value << std::endl;
	}
}
Logger.cpp
//----------------------------------------------------------------------------
//! @brief 通知を受け取る処理
//----------------------------------------------------------------------------
void Logger::onNotify(const std::string& event, int value) {
	std::cout << "[Log] Event=" << event << ", Value=" << value << std::endl;
}

addObserver/オブザーバーの追加

Subject/通知元Observer/監視者のポインタを持つことで、イベントが起こった際に監視者の通知を受け取る関数(本記事ではonNotify())を発火させます。

Player.h
	std::vector<IObserver*> observers_;	// !< 登録されているObserverのリスト
    
    //----------------------------------------------------------------------------
    //中略
    //----------------------------------------------------------------------------
	//----------------------------------------------------------------------------
	
    // Observerリストに追加を行う関数
	//! @param observer [in] 追加/削除するObserver
	//----------------------------------------------------------------------------
	void addObserver(IObserver* observer) override;
Player.cpp
//----------------------------------------------------------------------------
//! @brief 登録されているObserverに通知を送る関数
//----------------------------------------------------------------------------
void Player::notify(const std::string& event, int value) {
	for (auto* obs : observers_) {
		obs->onNotify(event, value);	// 各Observerに通知を送る
	}
}

//----------------------------------------------------------------------------
//! @brief ダメージを受ける関数
//----------------------------------------------------------------------------
void Player::takeDamage(int damage) {
	hp -= damage;				// ダメージ分HPを減らす
	if (hp < 0) hp = 0;			// HPが0未満にならないようにする
	notify("HP_CHANGED", hp);	// HPが変化したことを通知
}

発展形

発展形にはEventBusというものがあります。
興味のある方はぜひ以下の記事ものぞいてみて下さい。

総括

  • Observerパターンを使用することで、クラス間の疎結合を保ちながら拡張性の高い設計が行える
  • Subject側で通知を受け取りたいObserverのポインタを管理することで、イベント駆動設計を実現している
  • 乱用すると処理が重くなるというデメリットがある
  • 処理順序をつけたい場合には設計に工夫をする必要がある
14
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?