広告

Java : Template Method パターン (図解/デザインパターン)

Template Method パターンとは、GoF によって定義されたデザインパターンの1つです。
ベースとなるクラスで処理の大まかな流れを実装して、具体的な実装はサブクラスに任せる、という設計です。

本記事では、Template Method パターンを Java のコード付きで解説していきます。


デザインパターン(GoF) 関連記事

概要

Template Method パターンの目的は、ある処理のおおまかなアルゴリズムをあらかじめ決めておいて、そのアルゴリズムの具体的な設計をサブクラスに任せることである。

Template Method パターンは、

  • 大まかな処理はベースクラスで共通化
  • 具体的な処理はサブクラスで実装

という設計です。

大まかな処理は共通化して、処理の一部をサブクラスごとに切り替えたい、というときに便利です。

Template Method パターンの図

ベースクラスとサブクラスは次の図のような関係です。

クラス図

class Base {
}

class SubA extends Base {
}

class SubB extends Base {
}

class SubC extends Base {
}

ベースクラスを継承したのがサブクラスですね。
Template Method パターンでは、サブクラスが複数になることが一般的です。


【補足】

クラスの継承(extends) は、ベースクラスとそのサブクラスに強い依存を生みます。
代わりに Strategy パターン が使えないか検討してみるのもよいかもしれません。

強い依存の図

クラス図とコード例

クラス図

上の図は、Template Method パターンの一般的なクラス図です。

まずは AbstractClass を見てみましょう。

public abstract class AbstractClass {

    public void templateMethod() {
        method1();
        method2();
    }

    protected abstract void method1();

    protected abstract void method2();
}

AbstractClass は abstract キーワードがあるので 抽象クラス です。

public abstract class AbstractClass {
       ^^^^^^^^       ^^^^^^^^^^^^^ <---- 抽象クラス
    ...
}

また、method1method2 にも abstract キーワードがあります。
この2つは 抽象メソッド ですね。

protected abstract void method1();
          ^^^^^^^^      ^^^^^^^ <---- 抽象メソッド

protected abstract void method2();
          ^^^^^^^^      ^^^^^^^ <---- 抽象メソッド

templateMethodabstract キーワードがないので通常のメソッドです。

public void templateMethod() {
    ...
}

さて、Template Method パターンのキモはずばり、

  • 抽象クラスの 通常のメソッド から 抽象メソッド を呼び出す

ことにあります。

public void templateMethod() {
            ^^^^^^^^^^^^^^ <--- 通常のメソッド

    method1(); <--- 抽象メソッド呼び出し
    method2(); <--- 抽象メソッド呼び出し
}

AbstractClass から見ると、抽象メソッドである method1method2 は何が実行されるか分かりません。
サブクラスに処理をゆだねるわけですね。

そのサブクラスである ConcreteClass では、AbstractClass の抽象メソッドである method1method2 を実装します。

public class ConcreteClass extends AbstractClass {
    @Override
    protected void method1() {
        System.out.println("method1 is called!");
    }

    @Override
    protected void method2() {
        System.out.println("method2 is called!");
    }
}

この例では、ConcreteClass は1つだけですが、一般的には2つ以上になることが多いでしょう。

最後に、ConcreteClass を使う例です。

final AbstractClass ac = new ConcreteClass();
ac.templateMethod();

// 結果
// ↓
//method1 is called!
//method2 is called!

シーケンス図

AbstractClass(ベースクラス) の templateMethod を経由して、ConcreteClass(サブクラス) の method1method2 が無事に呼び出されました。

これが Template Method パターンです。


利用例

もう少し具体的な例で考えてみましょう。

クラス図

上の図のような Event クラスを考えます。

Event クラスは、action メソッドを実行することで、さまざまな処理を行います。
getType メソッドは、イベント種別を文字列で返します。

そして、具体的な処理はサブクラスで実装します。
今回、サブクラスは

  • 犬用のイベント : DogEvent
  • 猫用のイベント : CatEvent

の2つを作ることにしました。

Template Method を使わない例

まずは Template Method パターンを使わない例です。

チームリーダーの Aさんが、最初にベースとなる Event クラスを作りました。

public abstract class Event {

    private final String type;

    public Event(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }

    public abstract void action();
}

そして、そのサブクラスである DogEvent と CatEvent の実装を Bさんと Cさんにお願いしました。

  • Bさん : DogEvent
  • Cさん : CatEvent

さらに、A さんは1つ注文しました。

Event の action メソッドを呼び出したときに

  • イベント種別
  • action の開始と終了

が分かるようにログを出力してください、と。

今回は、ログは簡易的に System.out でコンソールに出力しましょう。

しばらくして、Bさんと Cさんの実装が終わりました。

public class DogEvent extends Event {
    public DogEvent() {
        super("犬イベント");
    }

    @Override
    public void action() {
        System.out.println("--- 開始 ---");
        System.out.println("種別 : " + getType());

        System.out.println("わんわん");

        System.out.println("--- 終了 ---");
    }
}
public class CatEvent extends Event {
    public CatEvent() {
        super("猫イベント");
    }

    @Override
    public void action() {
        System.out.println("start : " + getType());

        System.out.println("にゃーにゃー");

        System.out.println("end");
    }
}

さっそく、2つの Event を実行してみましょう。

final var events = List.of(
        new DogEvent(),
        new CatEvent()
);

for (final var event : events) {
    event.action();
    System.out.println();
}

// 結果
// ↓
//--- 開始 ---
//種別 : 犬イベント
//わんわん
//--- 終了 ---
//
//start : 猫イベント
//にゃーにゃー
//end

ログをどのように出力するか、というのを決めていなかったので、Bさんと Cさんとでフォーマットが微妙に違っていますね。
できればフォーマットは統一したいところです。

とはいえ、綿密にフォーマットを決めて、Bさんと Cさんで連携して同じように実装する、というのも大変です。
できれば、ログ出力という処理を 共通化 したいですね。


Template Method を使う例

チームリーダーの Aさんは、先ほどの Event クラスを修正することにしました。
Template Method パターンを導入します。

public abstract class Event {

    private final String type;

    public Event(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }

    public void action() {
        try {
            System.out.println("--- 開始 (" + type + ") ---");
            onAction();
        } finally {
            System.out.println("--- 終了 ---");
        }
    }

    protected abstract void onAction();
}

Event クラスの action メソッドで、共通で必要となるログを出力します。
onAction で例外が発生しても、action の "終了" ログが出力されるように try ~ finally 構文を使っています。

そして、実際のアクション処理は、抽象(abstract) メソッドである onAction で実装します。

public class DogEvent extends Event {
    public DogEvent() {
        super("犬イベント");
    }

    @Override
    protected void onAction() {
        System.out.println("わんわん");
    }
}
public class CatEvent extends Event {
    public CatEvent() {
        super("猫イベント");
    }

    @Override
    protected void onAction() {
        System.out.println("にゃーにゃー");
    }
}

サブクラス側は、ログ出力という余計なことは考えずに、アクション処理の実装に専念できます。
これは大きなメリットですね。サブクラス側はだいぶすっきりしました。

public class ErrorEvent extends Event {
    public ErrorEvent() {
        super("エラーイベント");
    }

    @Override
    protected void onAction() {
        throw new UnsupportedOperationException();
    }
}

ErrorEvent クラスは、意図的に 非チェック例外 を発生させるイベントです。

それでは、3つのイベントを実行してみましょう。

final var events = List.of(
        new DogEvent(),
        new CatEvent(),
        new ErrorEvent()
);

for (final var event : events) {
    event.action();
    System.out.println();
}

// 結果
// ↓
//--- 開始 (犬イベント) ---
//わんわん
//--- 終了 ---
//
//--- 開始 (猫イベント) ---
//にゃーにゃー
//--- 終了 ---
//
//--- 開始 (エラーイベント) ---
//--- 終了 ---

無事に、3つのイベントが実行できました。
ログのフォーマットも統一されていて良い感じですね。

まとめ

Template Method パターンは、

  • 大まかな処理はベースクラスで共通化
  • 具体的な処理はサブクラスで実装

という設計です。

大まかな処理は共通化して、処理の一部をサブクラスごとに切り替えたい、というときに便利です。

ただし、クラスの継承は強い依存も生むので、よく検討してから使いたいですね。


関連記事

ページの先頭へ