Chain of Responsibility¶
解決する問題¶
要求(イベント・リクエスト)を処理できる候補が複数あり、誰が処理すべきかを送信側が知らずに済ませたい。候補を鎖状に繋ぎ、処理できる者が現れるまで順に回す。
登場人物と責務¶
- Handler: 要求を処理するか、次へ回すかを決めるインターフェース
- ConcreteHandler: 処理条件と処理内容。処理しない場合は次のHandlerへ転送
- Client: 鎖の先頭に要求を投げるだけ
最小構成図¶
パターンなしの実装¶
// C++20(問題例)— 処理の優先順位と条件が1関数に密集
void ApplyDamage(Player& p, int damage) {
if (p.HasShield()) {
int absorbed = std::min(damage, p.ShieldValue());
p.SetShield(p.ShieldValue() - absorbed);
damage -= absorbed;
if (damage <= 0) return;
}
if (p.HasArmor()) { damage = damage * (100 - p.ArmorPercent()) / 100; }
if (p.HasBarrierBuff()) { p.ConsumeBarrier(); return; }
p.SetHp(p.Hp() - damage);
}
問題点¶
- 軽減要素の追加・削除・順序変更のたびにこの関数を書き換え(OCP)
- 装備・バフ・スキルなど別システム由来の軽減が1か所に集まり、全システムがここに依存
- 「このキャラだけ処理順が違う」を表現できない
パターン適用後のC++コード¶
// C++20
#include <memory>
struct DamageContext { int amount = 0; bool finished = false; };
class DamageHandler {
public:
virtual ~DamageHandler() = default;
void SetNext(std::unique_ptr<DamageHandler> next) { next_ = std::move(next); }
void Handle(DamageContext& ctx) {
Process(ctx);
if (!ctx.finished && next_) next_->Handle(ctx); // 未完なら次へ
}
protected:
virtual void Process(DamageContext& ctx) = 0;
private:
std::unique_ptr<DamageHandler> next_; // 鎖の次を所有
};
class ShieldHandler : public DamageHandler {
protected:
void Process(DamageContext& ctx) override {
int absorbed = std::min(ctx.amount, shield_);
shield_ -= absorbed;
ctx.amount -= absorbed;
if (ctx.amount <= 0) ctx.finished = true; // 吸収しきったら停止
}
private:
int shield_ = 30;
};
class ArmorHandler : public DamageHandler {
protected:
void Process(DamageContext& ctx) override { ctx.amount = ctx.amount * 70 / 100; }
};
class HpHandler : public DamageHandler { // 鎖の終端
protected:
void Process(DamageContext& ctx) override { hp_ -= ctx.amount; ctx.finished = true; }
private:
int hp_ = 100;
};
// 組み立て: shield->SetNext(armor); armor->SetNext(hp);
// キャラごとに鎖の構成・順序を変えられる
検証済みサンプル: samples/pattern_chain.cpp
C#またはUnityでの実装¶
Unity自身がこのパターンを内蔵しています: UIイベントのバブリング(uGUIの ExecuteEvents.ExecuteHierarchy は子→親へイベントを回し、処理した者で停止)、物理のレイヤーマトリクスの後段の当たり処理など。
// リスト方式(C#での実用形): 鎖のポインタ繋ぎよりリスト走査が扱いやすい
public interface IDamageStep { bool Process(ref int damage); } // trueで停止
public class DamagePipeline {
private readonly List<IDamageStep> steps = new(); // 順序 = リストの順
public void Add(IDamageStep s) => steps.Add(s);
public int Apply(int damage) {
foreach (var s in steps)
if (s.Process(ref damage)) break;
return damage;
}
}
ゲームでの具体例¶
- ダメージ軽減の段階処理(上の例)
- UI入力: クリックをボタン→パネル→画面の順に「処理できる者」へ(バブリング)
- 入力の消費: ポーズメニュー→UI→プレイヤー操作の優先度つきディスパッチ
- デバッグコマンドの解釈(コマンドを解釈できるモジュールに順に回す)
利点¶
- 処理段階の追加・削除・並べ替えが、鎖の組み替えだけでできる
- 各段の処理が独立したクラスに凝集(SRP)
- 送信側は先頭しか知らない(処理者の存在すら不定でよい)
欠点¶
- 処理される保証がない(終端まで素通りする可能性を設計に含める必要がある)
- 実行時に鎖を追わないと処理の全貌が分からない(デバッグしにくい)
- 段が少なく固定なら、if連鎖の方がはるかに読みやすい
適用条件¶
- 処理候補の数・順序が実行時・キャラごと・設定で変わる
- 段階ごとに独立した拡張(装備・バフの重ね合わせ)が繰り返し追加される
避けるべき条件¶
- 処理順が固定で3段以下 → 素直なif列で書く
- 全段が必ず処理に参加する(誰も止めない)→ それはDecoratorのリスト方式やパイプラインの方が意図に合う
似たパターンとの違い¶
- Decorator: 全員が参加する重ね掛け vs 誰かが止める選択的処理
- Command: 要求のオブジェクト化。Chainは要求の受け手側の構造。併用可(Commandを鎖に流す)
- Observer: 全購読者に配る(止まらない)vs 処理者を探して止まる
実務でよく見かける変形¶
- リスト+優先度方式(実務の主流): 鎖のポインタ管理より
sorted vector走査が単純で速い - ミドルウェアパイプライン(Webのmiddleware、Unityの
InputSystemのprocessors)
過剰設計になる例¶
「将来軽減の種類が増えるかも」で最初から鎖を組む——2種類の軽減が固定なら関数2行です。順序・構成が動く証拠が出てからで間に合います。
関連項目¶
理解度チェック¶
- DecoratorとChainの「参加の仕方」の違いは?
- 「処理されない可能性」への対処はどう設計しますか?
- リスト方式が鎖ポインタ方式より好まれる実務的理由は?
演習¶
「入力イベント(決定ボタン)」を ポーズ画面→ダイアログ→インベントリUI→プレイヤー操作 の優先順で1つだけに処理させる仕組みを、リスト方式で実装してください。「ダイアログ表示中はプレイヤーが動かない」ことがこの構造から自然に導かれることを確認してください。