Template Method¶
解決する問題¶
複数のクラスで処理の骨格(手順)は同じだが、一部の工程だけが違う。骨格を基底クラスに1回だけ書き、可変部分だけをサブクラスに上書きさせたい。
※ C++のテンプレート(template)とは無関係の用語です。「手順のひな型(template)」の意。
登場人物と責務¶
- AbstractClass: 骨格メソッド(template method)を持ち、可変工程を仮想関数(フック)として宣言
- ConcreteClass: フックだけを実装・上書きする
最小構成図¶
GameMode(基底)
RunMatch() { ← 骨格: 順序はここで固定。finalにして子に触らせない
Setup(); ← フック(純粋仮想: 必ず実装)
SpawnPlayers(); ← フック(デフォルトあり: 任意で上書き)
while(!IsMatchOver()) Tick(); ← フック
ShowResult(); ← フック
}
├ DeathmatchMode: IsMatchOver = キル数到達
└ RaceMode: IsMatchOver = ゴール到達
パターンなしの実装¶
// C++20(問題例)— モードごとに手順全体をコピペ
class DeathmatchMode {
public:
void RunMatch() {
LoadStage(); ResetScore(); SpawnAll(); // 共通部分(コピー1)
while (topKills_ < 10) TickBattle();
ShowRanking(); // 共通部分(コピー1)
}
};
class RaceMode {
public:
void RunMatch() {
LoadStage(); ResetScore(); SpawnAll(); // 共通部分(コピー2)…微妙に順番が違ったりする
while (!goalReached_) TickRace();
ShowRanking();
}
};
問題点¶
- 手順の共通部分がコピペされ、「マッチ開始の仕様変更」で全モードを修正(知識の重複 → DRY)
- コピペ時の微妙な差分(順序の違い)が意図か事故か分からなくなる
パターン適用後のC++コード¶
// C++20
class GameMode {
public:
virtual ~GameMode() = default;
// 骨格: 手順と順序はここだけが決める(virtualにしない=子に順序を触らせない)
void RunMatch() {
LoadStage();
Setup(); // 必須フック
SpawnPlayers(); // 任意フック(デフォルトあり)
while (!IsMatchOver()) Tick(); // 必須フック
ShowResult();
}
protected:
virtual void Setup() = 0; // 必ず違う部分: 純粋仮想
virtual void SpawnPlayers() { /* 標準の一斉スポーン */ } // 大抵同じ部分: デフォルト実装
virtual bool IsMatchOver() const = 0;
virtual void Tick() = 0;
private:
void LoadStage() { /* 共通 */ } // 子に見せる必要すらない共通部分
void ShowResult() { /* 共通 */ }
};
class DeathmatchMode : public GameMode {
protected:
void Setup() override { topKills_ = 0; }
bool IsMatchOver() const override { return topKills_ >= 10; }
void Tick() override { /* 戦闘更新 */ }
private:
int topKills_ = 0;
};
検証済みサンプル: samples/pattern_template_method.cpp
設計のコツ: 骨格は非virtual、フックはprotected virtual(Non-Virtual Interfaceイディオム)。子が上書きできる範囲を意図的に絞ります。
C#またはUnityでの実装¶
MonoBehaviourの Awake/Start/Update は、エンジンが骨格(ゲームループ)を持ち、あなたがフックを実装している Template Methodの巨大な実例です。UEの BeginPlay/Tick も同じ(→ Game Loop)。
public abstract class ScreenBase : MonoBehaviour { // 画面遷移の骨格
public async UniTask Open() { // 骨格: 順序を固定
gameObject.SetActive(true);
await OnLoadResources(); // フック
OnSetup(); // フック
await PlayOpenAnimation(); // フック(デフォルト: フェード)
}
protected abstract UniTask OnLoadResources();
protected abstract void OnSetup();
protected virtual UniTask PlayOpenAnimation() => FadeIn(0.2f);
}
ゲームでの具体例¶
- ゲームモード・ステージの共通フロー(準備→開始→終了判定→リザルト)
- 画面(Screen/Dialog)の開閉手順の共通化
- ターン制の1ターンの流れ(開始処理→行動→終了処理、ゲームごとに行動だけ違う)
- エンジンのライフサイクルフック(Awake/Start/Update、BeginPlay/Tick)
利点¶
- 手順の知識が1か所に(順序変更が基底の1修正で全モードに反映)
- 順序をサブクラスが壊せない(骨格が非virtualなら)
- 「何を実装すればモードが作れるか」が純粋仮想の一覧として明文化される
欠点¶
- 継承ベース: サブクラスは基底の実装に強く結合する(継承の弱点をそのまま持つ)。フックの追加・変更が全サブクラスに波及
- 実行時にフックの組み合わせを変えられない(型で固定)
- フックが増えると「どれを上書きすべきか」が分かりにくくなる(ドキュメント必須化)
- 基底と子の間で処理が行き来する(yo-yo problem)ため、読み手はクラス間を往復する
適用条件¶
- 手順(順序)が安定していて共通、可変点が明確に少数
- フレームワーク的な使い方(多数の実装者に「この3つを書けば動く」を提供)
避けるべき条件¶
- 可変点の組み合わせが実行時に変わる → Strategy(フックを注入に変える)
- 手順自体がモードごとに違う → 共通の骨格が嘘になる。無理に共通化しない
- 可変点が1つだけ → 関数1つの注入(コールバック)で十分
似たパターンとの違い¶
- Strategy: 同じ「可変点の差し替え」を、継承(Template Method)でやるかコンポジション(Strategy)でやるか。実行時差し替え・組み合わせが要るならStrategy、実装者への規約提供が目的で階層が浅いならTemplate Methodが簡潔
- Factory Method: Template Methodの骨格の中の「生成する工程」がフックになった特殊形
- Game Loop / Update Method: エンジンのTemplate Method的構造の実例
実務でよく見かける変形¶
- NVI(Non-Virtual Interface)イディオム: public非virtual+private virtualフック(C++の推奨形)
- フックのdelegate化: 継承の代わりに
std::function/eventをフックポイントに刺す(Strategyへの中間形)
過剰設計になる例¶
2つのモードの共通部分が「LoadStage 1行」だけなのに骨格クラスを作る——共通部分が薄い骨格は、将来の手順分岐で必ず窮屈になります。共通化は手順が本当に同じだと確認できてから。
関連項目¶
理解度チェック¶
- 骨格メソッドを非virtualにする理由は?
- Template MethodとStrategyの選択基準は?
- 「純粋仮想のフック」と「デフォルト実装のあるフック」の使い分けは?
演習¶
「クエスト実行の流れ(受注確認→開始演出→目標監視→報酬付与→完了演出)」をTemplate Methodで骨格化し、討伐クエストと収集クエストを実装してください。その後「開始演出だけ差し替えたい」要求に対し、継承のままとフック注入化のどちらが良いか検討してください。