Javaにおけるテンプレートメソッドパターン:定義済み足場による複雑なアルゴリズムの効率化
テンプレートメソッドデザインパターンの意図
操作におけるアルゴリズムの骨格を定義し、いくつかのステップをサブクラスに委ねます。テンプレートメソッドにより、サブクラスはアルゴリズムの構造を変更することなく、アルゴリズムの特定のステップを再定義できます。
実世界の例を用いたテンプレートメソッドパターンの詳細な説明
実世界の例
テンプレートメソッドパターンの現実世界の例えは、紅茶やコーヒーの準備に見ることができます。全体的なプロセス(アルゴリズム)は同じです。水を沸騰させ、飲み物を淹れ、カップに注ぎ、調味料を加えます。ただし、飲み物を淹れる具体的なステップは異なります。紅茶の場合、お茶の葉を熱湯に浸し、コーヒーの場合、挽いたコーヒー豆を淹れます。テンプレートメソッドパターンは、プロセス(沸騰、注ぎ、調味料の追加)の不変ステップを基底クラスにカプセル化し、サブクラスが特定の醸造ステップを定義できるようにすることで、温かい飲み物を作る全体的な構造の一貫性を保ちながら、必要な場所でカスタマイズを可能にします。
平易な言葉で言うと
Javaのテンプレートメソッドパターンは、親クラスのコアステップを概説し、子クラスが詳細な実装を調整できるようにすることで、Javaプログラミングにおけるコードの再利用性と設計の柔軟性を向上させます。
Wikipediaによると
オブジェクト指向プログラミングにおいて、テンプレートメソッドは、Gammaらが書籍「Design Patterns」で特定した行動デザインパターンの1つです。テンプレートメソッドは、スーパークラス(通常は抽象スーパークラス)にあるメソッドであり、多数の高度なステップによって操作の骨格を定義します。これらのステップは、テンプレートメソッドと同じクラス内の追加のヘルパーメソッドによって実装されます。
Javaにおけるテンプレートメソッドパターンのプログラム例
私たちのプログラミング例は、泥棒と盗難に関するものです。アイテムを盗む一般的なステップは同じです。まず、ターゲットを選択し、次に何らかの方法で彼を混乱させ、最後にアイテムを盗みます。ただし、これらのステップを実装する方法はたくさんあります。
まず、テンプレートメソッドクラスStealingMethod
を、その具体的な実装であるSubtleMethod
とHitAndRunMethod
とともに紹介します。サブクラスがテンプレートメソッドをオーバーライドしないようにするために、テンプレートメソッド(ここではsteal
メソッド)はfinal
として宣言する必要があります。そうしないと、基底クラスで定義されたスケルトンをサブクラスでオーバーライドできます。
@Slf4j
public abstract class StealingMethod {
protected abstract String pickTarget();
protected abstract void confuseTarget(String target);
protected abstract void stealTheItem(String target);
public final void steal() {
var target = pickTarget();
LOGGER.info("The target has been chosen as {}.", target);
confuseTarget(target);
stealTheItem(target);
}
}
@Slf4j
public class SubtleMethod extends StealingMethod {
@Override
protected String pickTarget() {
return "shop keeper";
}
@Override
protected void confuseTarget(String target) {
LOGGER.info("Approach the {} with tears running and hug him!", target);
}
@Override
protected void stealTheItem(String target) {
LOGGER.info("While in close contact grab the {}'s wallet.", target);
}
}
@Slf4j
public class HitAndRunMethod extends StealingMethod {
@Override
protected String pickTarget() {
return "old goblin woman";
}
@Override
protected void confuseTarget(String target) {
LOGGER.info("Approach the {} from behind.", target);
}
@Override
protected void stealTheItem(String target) {
LOGGER.info("Grab the handbag and run away fast!");
}
}
これが、テンプレートメソッドを含むハーフリングの泥棒クラスです。
public class HalflingThief {
private StealingMethod method;
public HalflingThief(StealingMethod method) {
this.method = method;
}
public void steal() {
method.steal();
}
public void changeMethod(StealingMethod method) {
this.method = method;
}
}
最後に、ハーフリングの泥棒が異なる盗難方法をどのように利用するかを示します。
public static void main(String[] args) {
var thief = new HalflingThief(new HitAndRunMethod());
thief.steal();
thief.changeMethod(new SubtleMethod());
thief.steal();
}
プログラム出力
11:06:01.721 [main] INFO com.iluwatar.templatemethod.StealingMethod -- The target has been chosen as old goblin woman.
11:06:01.723 [main] INFO com.iluwatar.templatemethod.HitAndRunMethod -- Approach the old goblin woman from behind.
11:06:01.723 [main] INFO com.iluwatar.templatemethod.HitAndRunMethod -- Grab the handbag and run away fast!
11:06:01.723 [main] INFO com.iluwatar.templatemethod.StealingMethod -- The target has been chosen as shop keeper.
11:06:01.723 [main] INFO com.iluwatar.templatemethod.SubtleMethod -- Approach the shop keeper with tears running and hug him!
11:06:01.723 [main] INFO com.iluwatar.templatemethod.SubtleMethod -- While in close contact grab the shop keeper's wallet.
Javaでテンプレートメソッドパターンを使用する状況
テンプレートメソッドパターンを使用すべき場合
- アルゴリズムの不変部分を一度実装し、可変的な動作を実装するのはサブクラスに任せる場合
- サブクラス間で共通の動作を共通クラスに集約して、コードの重複を避ける必要がある場合。これは、OpdykeとJohnsonが説明した「リファクタリングして一般化する」の良い例です。まず、既存のコードの違いを特定し、次に違いを新しい操作に分離します。最後に、異なるコードを、これらの新しい操作の1つを呼び出すテンプレートメソッドに置き換えます
- サブクラスの拡張を制御する場合。特定のポイントで「フック」操作を呼び出すテンプレートメソッドを定義することで、それらのポイントでのみ拡張を許可できます
テンプレートメソッドパターンJavaチュートリアル
Javaにおけるテンプレートメソッドパターンの実世界での応用
- Collections FrameworkのJavaのAbstractListクラスとAbstractSetクラスは、テンプレートメソッドパターンを使用して、リストおよびセット操作の一般的なアルゴリズムを定義します。
- JUnitのようなフレームワークは、テンプレートメソッドを使用して、テストケースのセットアップとティアダウンのプロセスを定義します。
テンプレートメソッドパターンの利点とトレードオフ
利点
- 基底クラスにアルゴリズムの不変部分を定義することにより、コードの再利用を促進します。
- 共通の動作を1箇所にカプセル化することで、コードの保守を簡素化します。
- アルゴリズムの特定のステップをサブクラスがオーバーライドできるようにすることで、柔軟性を高めます。
トレードオフ
- クラスの数が増加し、システムがより複雑になる可能性があります。
- サブクラスに公開されるステップが有用で意味のあるものになるように、慎重な設計が必要です。
関連するJavaデザインパターン
- ファクトリーメソッド:アルゴリズムの特定のステップに必要なオブジェクトを作成するために、テンプレートメソッドと組み合わせてよく使用されます。
- ストラテジー:テンプレートメソッドはアルゴリズムの骨格を定義し、サブクラスに特定のステップを実装させますが、ストラテジーパターンはアルゴリズムのファミリーを定義し、それらを交換可能にします。
- サブクラスサンドボックス:テンプレートメソッドを補完し、サブクラスが意図しない副作用を引き起こすことなく、アルゴリズムの特定のステップを安全にオーバーライドできるようにします。