責任連鎖パターン(Chain of Responsibility Pattern)
責任連鎖パターンは行動型設計パターンの代表で、リクエストの連鎖的な処理を実現します。本記事ではこのパターンの設計思想、C++での実装方法、および多様なアプリケーションケースを解説します。
責任連鎖パターンが必要な理由
- ログレベル分岐(DEBUG/INFO/ERROR)
- 多段承認プロセス
- 例外ハンドリング
- Webリクエストフィルタリング
- ゲームイベント処理
従来手法の問題点:
- コード重複
- 送信者・処理者の密結合
- 柔軟性の欠如
- 責務の過剰集中
パターン構造
[クライアント] → [ハンドラインターフェース] → [具体ハンドラA] → [具体ハンドラB] → ...
C++によるログシステム実装例
#include <iostream>
#include <memory>
#include <string>
enum class LogSeverity {
TRACE,
DEBUG,
INFO,
WARNING,
ERROR
};
class LogMessage {
public:
LogMessage(LogSeverity s, const std::string& m) : severity(s), message(m) {}
LogSeverity getSeverity() const { return severity; }
const std::string& getMessage() const { return message; }
private:
LogSeverity severity;
std::string message;
};
class LoggerBase {
public:
virtual ~LoggerBase() = default;
virtual void setNext(std::shared_ptr<LoggerBase> next) { this->next = next; }
virtual void process(const LogMessage& msg) = 0;
void passToNext(const LogMessage& msg) { if (next) next->process(msg); }
protected:
std::shared_ptr<LoggerBase> next;
};
class ConsoleLogger : public LoggerBase {
public:
ConsoleLogger(LogSeverity min_sev) : minSeverity(min_sev) {}
void process(const LogMessage& msg) override {
if (msg.getSeverity() >= minSeverity) {
std::cout << "_CONSOLE: " << msg.getMessage() << std::endl;
}
passToNext(msg);
}
private:
LogSeverity minSeverity;
};
class FileLogger : public LoggerBase {
public:
FileLogger(const std::string& path, LogSeverity min_sev) : filePath(path), minSeverity(min_sev) {
std::cout << "File logger initialized: " << filePath << std::endl;
}
void process(const LogMessage& msg) override {
if (msg.getSeverity() >= minSeverity) {
std::cout << "FILE(" << filePath << "): " << msg.getMessage() << std::endl;
}
passToNext(msg);
}
private:
std::string filePath;
LogSeverity minSeverity;
};
// ロギングチェーンの構築
auto build_logger_chain() {
auto chain = std::make_shared<ConsoleLogger>(LogSeverity::DEBUG);
chain->setNext(std::make_shared<FileLogger>("app.log", LogSeverity::INFO));
return chain;
}
int main() {
auto logger_chain = build_logger_chain();
logger_chain->process(LogMessage{LogSeverity::DEBUG, "システム起動"});
logger_chain->process(LogMessage{LogSeverity::INFO, "初期化完了"});
return 0;
}
責任連鎖パターンの主要利点
- 送信者と受信者の完全分離
- 実行時の処理フロー変更可能
- 新しいハンドラの即時追加
- 単一責任原則の実現
- 処理フローの明確な制御
進階応用例
1. チェーン中断機能
class CriticalHandler : public LoggerBase {
public:
void process(const LogMessage& msg) override {
if (msg.getSeverity() == LogSeverity::ERROR) {
std::cerr << "CRITICAL ERROR: " << msg.getMessage() << std::endl;
return;
}
passToNext(msg);
}
};
2. 平行処理構造
class ParallelHandler : public LoggerBase {
public:
void addBranch(std::shared_ptr<LoggerBase> branch) {
branches.push_back(branch);
}
void process(const LogMessage& msg) override {
for (auto& b : branches) {
b->process(msg);
}
}
private:
std::vector branches;
};
実装上の考慮点
| 課題 | 対策 |
|---|---|
| 未処理リクエスト | デフォルトハンドラの設定 |
| パフォーマンス | ハンドラ順序最適化 |
| デバッグ困難 | 処理トレース機能追加 |
関連パターン比較
| パターン | 類似点 | 違い |
|---|---|---|
| デコレータパターン | チェーン構造利用 | 機能拡張 vs リクエスト処理 |
| コマンドパターン | 組み合わせ可能 | リクエストカプセル化 vs チェーン処理 |