Template Method
一言でいうと
Template Method (テンプレート・メソッド、 鋳型法) は、 振る舞いに関するデザインパターンの一つで、 スーパークラス内でアルゴリズムの骨格を定義しておき、 サブクラスは構造を変えることなくアルゴリズムの特定のステップを上書きします。
![Template Method デザインパターン](/images/patterns/content/template-method/template-method.png?id=eee9461742f832814f19612ccf472819)
問題
企業文書を分析するデータマイニングのアプリケーションを作成しているところを想像してみてください。 ユーザーは、 様々な形式 (PDF、 DOC、 CSV) の文書をアプリに送り付け、 アプリはこれらの文書から何か意味のあるデータを統一的な形式で抽出しようとします。
アプリの最初のバージョンは DOC ファイルでのみ動作します。 次のバージョンでは、 CSV ファイルをサポートすることができました。 1 か月後には、 PDF ファイルからデータを抽出する方法をそれに 「教えました」。
![データマイニングのクラス間の多数の重複したコード](/images/patterns/diagrams/template-method/problem.png?id=60fa4f735c467ac1c9438231a1782807)
データ・マイニングのクラス間にある多くの重複したコード。
ある時点で、 三つのクラスすべてに似たようなコードがあることに気がつきました。 様々なデータ形式を扱うためのコードは、 すべてのクラスでまったく異なりますが、 データ処理と解析のためのコードはほぼ同じです。 アルゴリズムの構造をそのままにして、 コードの重複を取り除けたらどんなに素晴らしいでしょう?
これらのクラスを使用するクライアント側のコードにも一つ問題があります。 処理オブジェクトのクラスに応じて適切な処理を選ぶための条件文だらけになっている、 ということです。 もしも処理クラスの全部が共通のインターフェースか基底クラスを持てれば、 クライアント・コードの条件分を削除でき、 処理オブジェクトのメソッドを呼ぶ際に多相性を利用できます。
解決策
Template Method パターンでは、 アルゴリズムを一連のステップに分解し、 これらのステップをメソッドに書き換え、 単一のテンプレート・メソッドからこれらのメソッドを順番に呼び出すようにします。 ステップは、 abstract
としてもいいですし、 何らかのデフォルトの実装を用意してもかまいません。 アルゴリズムを利用するためには、 クライアントは独自のサブクラスを用意し、 全抽象メソッドを実装し、 必要ならば他のメソッド (ただしテンプレート・メソッドは除く) も上書きする決まりになっています。
データマイニング・アプリでこれがどうなるか見てみましょう。 三つの解析アルゴリズムすべてに対する基底クラスを作成できます。 このクラスは、 様々な文書処理ステップへの一連の呼び出しからなるテンプレート・メソッドを定義します。
![テンプレート・メソッドはアルゴリズムの骨組みを定義する](/images/patterns/diagrams/template-method/solution-ja.png?id=7a9848f9797e4a9a0d020eb547397e6a)
Template Method は、 アルゴリズムをステップに分割し、 サブクラスがこれらのステップを上書きするが、 本物のメソッドではない。
最初は、 すべてのステップを abstract
と宣言し、 サブクラスにこれらのメソッドの独自の実装を強いるようにできます。 我々の例題では、 サブクラスに必要な実装がすでにすべてあるので、 残されたことは、 必要に応じてスーパークラスのメソッドに合うようにメソッドのシグネチャーを調整することぐらいです。
ここで、 重複したコードを取り除くために何ができるか考えてみましょう。 ファイルを開けたり閉じたりするためのコードや、 データの抽出・解析に関わるコードは、 データ形式により異なるため、 この辺のコードはいじる必要ないみたいですね。 しかし、 生データの分析やレポートの作成などの他のステップの実装はサブクラス間で非常に類似しているので、 基底クラスに移動すれば、 サブクラスから共有できそうです。
ステップには二つの種類があります:
- 抽象ステップは、 全サブクラスで実装が必要。
- オプションのステップは、 何らかのデフォルト実装があり、 必要な場合だけ上書き可能。
実はもう一つフックという種類のステップがあります。 フックは、 空のデフォルト実装のオプションのステップです。 フックが上書きされてなくても、 テンプレート・メソッドは動きます。 フックは通常アルゴリズムの重要なステップの前後に置かれ、 サブクラスにアルゴリズムの更なる拡張箇所を提供します。
現実世界でのたとえ
![大規模住宅建設](/images/patterns/diagrams/template-method/live-example.png?id=2485d52852f87da06c9cc0e2fd257d6a)
典型的な建築プランは、 顧客の要望に合わせてわずかに変更することが可能。
Template Method のやり方は、 大規模住宅建設に使うことができます。 標準的な家を建てるための建築プランに、 購買予定者が細かい部分を調整できるよう、 いくつかの変更可能箇所を含めることができます。
たとえば、 基礎工事、 骨組み、 壁面の設置、 下水の設置、 水道の配管、 電気の配線などを少し変えて、 他とちょっと違った家を建てることができます。
構造
![Template Method デザインパターンの構造](/images/patterns/diagrams/template-method/structure.png?id=924692f994bff6578d8408d90f6fc459)
![Template Method デザインパターンの構造](/images/patterns/diagrams/template-method/structure-indexed.png?id=4ced6107519bc66710d2f05c0f4097a1)
-
抽象クラス (Abstract Class) は、 アルゴリズムの各ステップに対応するメソッドと、 これらのメソッドを特定の順序で呼び出す実際のテンプレート・メソッドを宣言します。 ステップは、
abstract
と宣言されるか、 何らかのデフォルトの実装を持つかのどちらかです。 -
具象クラス (Concrete Class) は、 テンプレート・メソッドを除くすべてのステップを上書きできます。
擬似コード
この例では、 Template Method パターンが、 簡単な戦略ビデオ・ゲームで、 人工知能の様々な戦略に対する 「骨組み」 を提供します。
![Template Method パターン例の構造](/images/patterns/diagrams/template-method/example.png?id=c0ce5cc8070925a1cd345fac6afa16b6)
単純なビデオ・ゲームの AI クラスたち
ゲーム内のすべての種族は、 ほぼ同じ種類の部隊と建物を所有しています。 そのため、 同じ AI の構造を様々な種族用に再利用できます。 いくつかの細かいことを上書きするだけです。 このやり方で、 オーク (Orc) の AI を上書きして、 より攻撃的にする、 人類を防衛指向にする、 モンスターは何も建設できないようにする、 などができます。 新しい種族をゲームに追加するには、 新規の AI のサブクラスを作成し、 基底 AI クラスで宣言されたデフォルトのメソッドを上書きします。
// この抽象クラスは、何らかのアルゴリズムの骨組みを含むテンプレート・メ
// ソッドを一つ定義する。このメソッドは、通常は abstract と宣言された原始
// 操作メソッドに対する呼び出しから構成される。具象サブクラスは、これらの
// 原始操作を実装するが、テンプレート・メソッドはそのままにする。
class GameAI is
// テンプレート・メソッドはアルゴリズムの骨組みを定義する。
method turn() is
collectResources()
buildStructures()
buildUnits()
attack()
// ステップのいくつかは、基底クラスで定義されているかもしれない。
method collectResources() is
foreach (s in this.builtStructures) do
s.collect()
// そしていくつかは、abstract と定義されているかもしれない。
abstract method buildStructures()
abstract method buildUnits()
// クラスには、複数のテンプレート・メソッドがあってもよい。
method attack() is
enemy = closestEnemy()
if (enemy == null)
sendScouts(map.center)
else
sendWarriors(enemy.position)
abstract method sendScouts(position)
abstract method sendWarriors(position)
// 具象クラスは、基底クラス中の全ての抽象操作(メソッド)を実装しなければ
// ならないが、テンプレート・メソッドを上書きしてはならない。
class OrcsAI extends GameAI is
method buildStructures() is
if (there are some resources) then
// 農場を建設し、兵舎を建設し、拠点を築く。
method buildUnits() is
if (there are plenty of resources) then
if (there are no scouts)
// ピオンを作り、それを偵察隊グループに追加。
else
// グラントを作り、それを戦闘員グループに追加。
// ……
method sendScouts(position) is
if (scouts.length > 0) then
// 偵察隊を配置につける。
method sendWarriors(position) is
if (warriors.length > 5) then
// 兵隊を配置につける。
// サブクラスでは、デフォルト実装付きの操作のいくつかを上書きすることもで
// きる。
class MonstersAI extends GameAI is
method collectResources() is
// モンスターは資源を集めない。
method buildStructures() is
// モンスターは構造物を建設しない。
method buildUnits() is
// モンスターは部隊を編成しない。
適応性
クライアントにアルゴリズムの特定のステップのみを拡張させたいが全体のアルゴリズムや構造は変えたくない場合に、 Template Method パターンを使用します。
Template メソッドを使用すると、 一枚岩のアルゴリズムを、 一連のステップに分割し、 サブクラスで簡単に拡張できるようにできます。 スーパークラスで定義された構造は、 そのまま維持されます。
少数の些細な違いを除いてほとんど同じアルゴリズムのクラスがいくつかある場合、 このパターンを使用します。 結果として、 アルゴリズムを変更する場合は、 すべてのクラスを変更する必要があるかもしれません。
アルゴリズムをテンプレート・メソッドに転換する時に、 類似の実装を含むステップをスーパークラスに移動して、 コードの重複を削除できます。 サブクラス間で異なるコードはサブクラスに留めます。
実装方法
-
対象となるアルゴリズムを分析し、 それをステップに分割できるかどうかを検討します。 どのステップがサブクラス間で共通で、 どのステップが常にサブクラスに固有かを考察します。
-
抽象基底クラスを作成し、 テンプレート・メソッドと、 アルゴリズムの各ステップに対応する抽象メソッドの組を宣言します。 アルゴリズムの輪郭は、 ステップを実行するテンプレート・メソッドに描かれています。 テンプレート・メソッドがサブクラスで上書きされるのを防ぐために、 テンプレート メソッドを
final
と宣言することを検討してください。 -
全部のステップが抽象メソッドとなってしまったとしても、 大丈夫です。 いくつかのメソッドにはデフォルトの実装があった方が有益かもしれません。 サブクラスがそれらのメソッドを実装する必要がなくなります。
-
アルゴリズムの重要なステップの間にフックを追加することを検討してください。
-
アルゴリズムの異種ごとに、 新しい具象サブクラスを作成します。 サブクラスは、 抽象ステップのすべてを実装しなければならないですが、 任意のステップは上書きしてもよいです。
長所と短所
- クライアントは、 大きなアルゴリズムの特定の部分だけを上書き可能。 アルゴリズムの他部分への変更の影響を受けにくい。
- 重複コードをスーパークラスに取り入れ可能。
- いくつかのクライアントにとっては、 与えられたアルゴリズムの骨組みは、 そのロジックを表現するために不十分である可能性。
- サブクラスでステップのデフォルト実装を抑制することは、 リスコフの置換原則違反の可能性。
- ステップ数が多いほど、 テンプレート・メソッドの保守が困難となりがち。
他のパターンとの関係
-
Factory Method は、 Template Method の特別な場合です。 同時に、 Factory Method は、 大きな Template Method の一つのステップとして使うこともできます。
-
Template Method は、 継承に基づくもので、 サブクラスでその一部を拡張することによって、 アルゴリズムを部分的に変更できます。 Strategy は、 合成に基き、 オブジェクトの振る舞いを、 新しい振る舞いに関連した異なるストラテジーを与えることにより変更します。 Template Method は、 クラスのレベルで機能するので、 静的です。 Strategy はオブジェクトのレベルで機能するため、 実行時に動作を切り替えることができます。