Java : Template Method パターン (図解/デザインパターン)
Template Method パターンとは、GoF によって定義されたデザインパターンの1つです。
ベースとなるクラスで処理の大まかな流れを実装して、具体的な実装はサブクラスに任せる、という設計です。
本記事では、Template Method パターンを Java のコード付きで解説していきます。
デザインパターン(GoF) 関連記事
- 生成に関するパターン
- 振る舞いに関するパターン
概要
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 {
^^^^^^^^ ^^^^^^^^^^^^^ <---- 抽象クラス
...
}
また、method1 と method2 にも abstract キーワードがあります。
この2つは 抽象メソッド ですね。
protected abstract void method1();
^^^^^^^^ ^^^^^^^ <---- 抽象メソッド
protected abstract void method2();
^^^^^^^^ ^^^^^^^ <---- 抽象メソッド
templateMethod は abstract キーワードがないので通常のメソッドです。
public void templateMethod() {
...
}
さて、Template Method パターンのキモはずばり、
- 抽象クラスの 通常のメソッド から 抽象メソッド を呼び出す
ことにあります。
public void templateMethod() {
^^^^^^^^^^^^^^ <--- 通常のメソッド
method1(); <--- 抽象メソッド呼び出し
method2(); <--- 抽象メソッド呼び出し
}
AbstractClass から見ると、抽象メソッドである method1 と method2 は何が実行されるか分かりません。
サブクラスに処理をゆだねるわけですね。
そのサブクラスである ConcreteClass では、AbstractClass の抽象メソッドである method1 と method2 を実装します。
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(サブクラス) の method1 と method2 が無事に呼び出されました。
これが 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 パターンは、
- 大まかな処理はベースクラスで共通化
- 具体的な処理はサブクラスで実装
という設計です。
大まかな処理は共通化して、処理の一部をサブクラスごとに切り替えたい、というときに便利です。
ただし、クラスの継承は強い依存も生むので、よく検討してから使いたいですね。
関連記事
- 標準APIにならう命名規則
- コメントが少なくて済むコードを目指そう
- シングルトン・パターンの乱用はやめよう
- メソッドのパラメータ(引数)は使う側でチェックしよう
- 不変オブジェクト(イミュータブル) とは
- 依存性の注入(DI)をもっと気軽に
- 不要になったコードはコメントアウトで残さずに削除しよう
- 簡易的な Builder パターン
- 読み取り専用(const) のインタフェースを作る
- 図解/デザインパターン一覧 (GoF)
- Abstract Factory パターン
- Adapter パターン
- Bridge パターン
- Builder パターン
- Chain of Responsibility パターン
- Command パターン
- Composite パターン
- Decorator パターン
- Facade パターン
- Factory Method パターン
- Flyweight パターン
- Interpreter パターン
- Iterator パターン
- Mediator パターン
- Memento パターン
- Observer パターン
- Prototype パターン
- Proxy パターン
- Singleton パターン
- State パターン
- Strategy パターン
- Visitor パターン