デザインパターンの章

6.3. Template Method

具体的な処理をサブクラス側にまかせる為のパターンです。

スーパークラスで処理の枠組みを決めておいてサブクラスでその具体的な処理内容を定めます。 ある一つの大きな処理を複数のステップに分割して、ステップの実行順序を守りながら ステップごとに処理内容を変更できる仕組みを提供することができます。

大きな処理に対して部分的な変更を行う目的に適しているといえるでしょう。

■クラス図

■サンプルソース

これは、テンプレートメソッド(枠組み)を定義する抽象クラスです。

public abstract class AbstractDisplay {
    protected abstract void open();  /* 「開く」抽象メソッド。 */
    protected abstract void print(); /* 「出力」抽象メソッド。 */
    protected abstract void close(); /* 「閉じる」抽象メソッド。 */

    /** テンプレートメソッド。 */
    public final void display() {
        open();                          /* 開く。 */
        for (int i = 0; i < 5; i++) { /* 5回出力。 */
            print();
        }
        close();                         /* 閉じる。 */
    }
}

テンプレートメソッドにで大まかな処理の流れを定義しています。
ここでは「open() -> print() x 5 -> close()」としました。

各々の具体的な細かい処理内容は記述しません。
あくまでも流れだけを決めます。

これは、具体的な処理を定める1つ目のサブクラスです。

public class CharDisplay extends AbstractDisplay {
    private char ch;
    public CharDisplay(char ch) {
        this.ch = ch;
    }
    @Override
    protected void open() {  /* "<<"を開始文字列として表示。 */
        System.out.print("<<");
    }
    @Override
    protected void print() { /* chを表示。 */
        System.out.print(ch);
    }
    @Override
    protected void close() { /* ">>"を終了文字列として表示。 */
        System.out.println(">>");
    }
}

具体的な細かい処理内容を記述しています。
スーパークラス側でアルゴリズムが記述されるため、 サブクラス側ではアルゴリズムを記述する必要がありません。

これは、具体的な処理を定める2つ目のサブクラスです。

public class StringDisplay extends AbstractDisplay {
    private String str;
    public StringDisplay(String str) {
        this.str = str;
    }
    @Override
    protected void open() {  /* "/////"を開始文字列として表示。 */
        System.out.print("/////");
    }
    @Override
    protected void print() { /* strを表示。 */
        System.out.print(str);
    }
    @Override
    protected void close() { /* "\\\\\"を開始文字列として表示。 */
        System.out.println("\\\\\");
    }
}

■実装のミソ

1.
大きな処理をスーパークラス側で一つのテンプレートメソッドとしてアルゴリズムを記述します。

2.
内部の細かい処理を別メソッドとして括り出してサブクラスでオーバーライドして実装します。

■利用例

void Test() {
    AbstractDisplay charDisplay = new CharDisplay('z');
    AbstractDisplay stringDisplay = new StringDisplay("オレオレ");
    AbstractDisplay stringDisplay2 = new StringDisplay("");
    
    charDisplay.display();
    stringDisplay.display();
    stringDisplay2.display();
}

CharDisplay、StringDisplay ともに AbstractDisplay のサブクラスです。
よって display() を呼び出すことができます。
実際の動作は個々のクラスによって定まります。

このパターンを適用せずにソースのコピー&ペーストをしてしまうと、 同じようなアルゴリズムがソースのあちこちに散らばってしまうでしょう。 アルゴリズムにバグが発見されたとすると、 コピー&ペーストした部分を全て書き直す事態に陥ります。

このパターンを使えばアルゴリズムが記載されている箇所は抽象クラスの1ヶ所だけです。 アルゴリズムに誤りが見つかったとしても修正すべき箇所は1ヶ所だけで済みますね。

■注意点

サブクラスを実装する際に、スーパークラスのテンプレートメソッドが どんなアルゴリズムを組んでいるのか理解しなければ、 抽象メソッドを実装することはできないでしょう。 スーパークラスがどのような意図を持って提供しているのか理解して サブクラスでメソッドを実装してください。

このパターンは、型にがっちりはめるのには有効な手法です。 しかしあまりにもスーパークラス側でアルゴリズムをがっちり固めてしまうと サブクラス側での自由度が減ってしまいます。 型に当てはまらないケースが生じた場合に融通が利かず、むしろ足かせとなります。

スーパークラス側でアルゴリズムの記述を減らすと サブクラス側の自由度は上がり融通は利きますが、 記述量が多くなり処理の重複が生じてきます。

どこまでをテンプレートメソッドとして提供すべきかのボーダーラインを 明確に決めることはできません。ボーダーを決めるのは設計者の裁量に任されます。

さじ加減によっては自分の首を絞めかねないので、気をつけて適用しましょう。

< 前のページへ

Pagetop