責任連鎖パターンの実践ガイド

責任連鎖パターン(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. 送信者と受信者の完全分離
  2. 実行時の処理フロー変更可能
  3. 新しいハンドラの即時追加
  4. 単一責任原則の実現
  5. 処理フローの明確な制御

進階応用例

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 チェーン処理

タグ: C++ 設計パターン ソフトウェア設計 リクエスト処理 OOP

6月17日 18:50 投稿