Template Method パターンでアルゴリズムの骨格を定義：料理レシピシミュレーターの実装

## はじめに

ソフトウェア開発において、似たような処理を持つが細部が異なるクラスを設計する場合があります。このような状況で、コードの重複を避けつつ、柔軟性を保つための設計パターンの一つが「Template Methodパターン」です。

本記事では、Template Methodパターンの概要、使用理由、適用場面、メリット、デメリットを解説し、料理レシピシミュレーターを例にした実装を通じて、このパターンの実践的な使用方法を紹介します。

なお、この記事のコード例はPython 3.6以上のバージョンを想定しています。

## Template Methodパターンとは

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/03c3886c-17d0-a27e-c319-dc2487b2ebb3.png)

Template Methodパターンは、アルゴリズムの骨格を抽象クラスで定義し、一部の手順を子クラスで実装するデザインパターンです。親クラスでアルゴリズムの基本的な流れを定義し、具体的な実装を子クラスに委ねることで、コードの再利用性と拡張性を高めます。

## なぜTemplate Methodパターンを使うのか

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/d23fa615-9549-b62c-d902-62ae2c142789.png)

1. コードの再利用性向上：共通の処理を親クラスにまとめることで、コードの重複を減らせます。
2. アルゴリズムの一貫性保持：基本的な流れを親クラスで定義することで、アルゴリズムの一貫性を保てます。
3. 拡張性の確保：新しい実装を追加する際に、既存のコードを変更せずに子クラスを追加するだけで済みます。

## 使い所

- 複数のクラスで共通の処理手順があり、一部の手順だけが異なる場合
- アルゴリズムの骨格は固定したまま、特定のステップの実装を変更したい場合
- コードの重複を避けたい場合

## メリット

1. コードの重複の削減：共通処理を親クラスにまとめることで、コードの重複を減らせます。
2. 拡張性の向上：新しいアルゴリズムのバリエーションを追加する際に、既存のコードを変更せずに済みます。
3. 保守性の向上：アルゴリズムの基本構造が一箇所にまとまるため、全体の把握や修正が容易になります。
4. 制御の反転：親クラスがアルゴリズムの流れを制御し、子クラスが特定の処理を実装するため、制御の反転が実現できます。

## デメリット

1. 継承の使用：継承を使用するため、以下のような制限がある場合があります。
   - 継承による結合度の上昇：親クラスと子クラスの結合度が高くなり、変更に対する影響範囲が広がる可能性があります。
   - 単一継承の制約：多重継承がサポートされていない言語では、他のクラスからも継承したい場合に制約が生じることがあります。
2. 複雑さの増加：小規模なプロジェクトでは、パターンの導入が過剰な設計になる可能性があります。
3. デバッグの難しさ：処理が親クラスと子クラスに分散するため、デバッグが複雑になる場合があります。
4. 理解の難しさ：初心者には理解しづらい場合があります。

## 実装例：料理レシピシミュレーター

それでは、Template Methodパターンを使用して、面白くて動作確認しやすい料理レシピシミュレーターを実装してみましょう。この例では、異なる料理（ここではパスタとサラダ）の調理手順を表現します。

```mermaid
classDiagram
    class Recipe {
        <<abstract>>
        +cook()
        #prepare_ingredients()*
        #cook_main_dish()*
        #add_seasonings()*
        -serve()
    }
    class PastaRecipe {
        #prepare_ingredients()
        #cook_main_dish()
        #add_seasonings()
    }
    class SaladRecipe {
        #prepare_ingredients()
        #cook_main_dish()
        #add_seasonings()
    }
    Recipe <|-- PastaRecipe
    Recipe <|-- SaladRecipe

    note for Recipe "抽象クラス：\n- アルゴリズムの骨格を定義\n- テンプレートメソッド(cook)を実装\n- 抽象メソッドを宣言"
    note for PastaRecipe "具象クラス：\n- 抽象メソッドを具体的に実装\n- パスタ特有の調理手順を定義"
    note for SaladRecipe "具象クラス：\n- 抽象メソッドを具体的に実装\n- サラダ特有の調理手順を定義"

    note "Template Methodパターン\n\n1. テンプレートメソッド(cook)：\n   全体の流れを制御\n\n2. 抽象メソッド：\n   サブクラスで必ず実装\n\n3. フックメソッド(オプション)：\n   サブクラスで必要に応じて\n   オーバーライド\n\n4. 具象メソッド(serve)：\n   共通の実装を提供"
```

```mermaid
sequenceDiagram
    participant Client
    participant PastaRecipe
    participant Recipe

    Client->>PastaRecipe: new PastaRecipe()
    Client->>PastaRecipe: cook()
    activate PastaRecipe
    PastaRecipe->>Recipe: cook()
    activate Recipe
    Recipe->>PastaRecipe: prepare_ingredients()
    activate PastaRecipe
    PastaRecipe-->>Recipe: 材料準備完了
    deactivate PastaRecipe
    Recipe->>PastaRecipe: cook_main_dish()
    activate PastaRecipe
    PastaRecipe-->>Recipe: 主要調理工程完了
    deactivate PastaRecipe
    Recipe->>PastaRecipe: add_seasonings()
    activate PastaRecipe
    PastaRecipe-->>Recipe: 味付け完了
    deactivate PastaRecipe
    Recipe->>Recipe: serve()
    Recipe-->>PastaRecipe: 調理完了
    deactivate Recipe
    PastaRecipe-->>Client: 料理の完成
    deactivate PastaRecipe

    note over Recipe: テンプレートメソッド
    note over PastaRecipe: 具象クラスでの実装
    note over Client,Recipe: Template Methodパターンの流れ
```

```python
from abc import ABC, abstractmethod
import time

class Recipe(ABC):
    def cook(self):
        # アルゴリズムの骨格を定義するテンプレートメソッド
        self.prepare_ingredients()
        self.cook_main_dish()
        self.add_seasonings()
        self.serve()

    @abstractmethod
    def prepare_ingredients(self):
        pass

    @abstractmethod
    def cook_main_dish(self):
        pass

    @abstractmethod
    def add_seasonings(self):
        pass

    def serve(self):
        print("料理の完成です！召し上がれ！🍽️")

def simulate_cooking(duration):
    knives = "🔪" * int(duration)
    print(f"調理中...{knives}")
    time.sleep(0.5)  # 実際の待ち時間を短縮
    print("完了！")

class PastaRecipe(Recipe):
    def prepare_ingredients(self):
        print("パスタの材料を準備します：")
        print("- スパゲッティ")
        print("- トマト")
        print("- ニンニク")
        print("- オリーブオイル")

    def cook_main_dish(self):
        print("パスタを茹でています...")
        simulate_cooking(3)
        print("具材を炒めています...")
        simulate_cooking(2)

    def add_seasonings(self):
        print("塩、こしょう、パルメザンチーズを加えます")

class SaladRecipe(Recipe):
    def prepare_ingredients(self):
        print("サラダの材料を準備します：")
        print("- レタス")
        print("- トマト")
        print("- キュウリ")
        print("- ニンジン")

    def cook_main_dish(self):
        print("野菜を洗っています...")
        simulate_cooking(2)
        print("野菜を切っています...")
        simulate_cooking(2)

    def add_seasonings(self):
        print("ドレッシングをかけます")

def cook_meal(recipe):
    print(f"\n{recipe.__class__.__name__}の調理を開始します！\n")
    recipe.cook()
    print("\n")

if __name__ == "__main__":
    pasta = PastaRecipe()
    salad = SaladRecipe()

    cook_meal(pasta)
    cook_meal(salad)
```

この実装例では、`Recipe`抽象基底クラスでTemplate Methodパターンを使用しています。`cook`メソッドがテンプレートメソッドとなり、料理の基本的な手順を定義しています。具体的なレシピは、`PastaRecipe`と`SaladRecipe`クラスで各メソッドをオーバーライドすることで実装されています。

## 実行結果

このコードを実行すると、以下のような出力が得られます：

```
PastaRecipeの調理を開始します！

パスタの材料を準備します：
- スパゲッティ
- トマト
- ニンニク
- オリーブオイル
パスタを茹でています...
調理中...🔪🔪🔪
完了！
具材を炒めています...
調理中...🔪🔪
完了！
塩、こしょう、パルメザンチーズを加えます
料理の完成です！召し上がれ！🍽️


SaladRecipeの調理を開始します！

サラダの材料を準備します：
- レタス
- トマト
- キュウリ
- ニンジン
野菜を洗っています...
調理中...🔪🔪
完了！
野菜を切っています...
調理中...🔪🔪
完了！
ドレッシングをかけます
料理の完成です！召し上がれ！🍽️
```

## この実装例の特徴

1. **視覚的フィードバック**: 調理過程を絵文字や時間経過のシミュレーションで表現し、ユーザーが調理の進行を視覚的に理解できるようにしています。

2. **実際の調理過程の模倣**: 材料の準備、調理、味付けなど、実際の料理の手順を模倣しており、直感的に理解しやすくなっています。

3. **拡張性**: 新しい料理レシピを追加したい場合、`Recipe`クラスを継承して新しいクラスを作成するだけで簡単に拡張できます。

4. **共通処理の集約**: `serve`メソッドのように、全てのレシピで共通の処理は親クラスで一元管理されています。

5. **柔軟性**: 各料理レシピの具体的な調理手順は、それぞれのクラスで自由に定義できます。

この実装例を通じて、Template Methodパターンがどのように異なるアルゴリズム（この場合は料理レシピ）の共通構造を定義し、同時に個別の実装の自由度を保っているかを確認できます。

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/3ffa894e-decb-ae89-b0a3-1a8cd8ff0fb8.png)

Template Methodパターンは、アルゴリズムの骨格を定義しつつ、細部の実装を子クラスに委ねることで、コードの再利用性と拡張性を高めるデザインパターンです。料理レシピシミュレーターの例で示したように、共通の処理を親クラスにまとめ、異なる部分のみを子クラスで実装することで、効率的かつ柔軟なコード設計が可能になります。

特に重要な点は、このパターンが実現する「制御の反転（Inversion of Control）」です。親クラスがアルゴリズムの流れを制御し、子クラスが具体的な実装を提供することで、コードの拡張性と柔軟性が高まります。これにより、新しい機能追加や変更が容易になり、ソフトウェアの進化に柔軟に対応できるようになります。

この例を通じて、Template Methodパターンが単なる理論的な概念ではなく、実際のプログラミングシナリオでどのように適用され、コードの構造化と拡張性の向上にどのように貢献するかを理解できるでしょう。

新しいレシピの追加や、調理プロセスの詳細化など、このコードをベースにさまざまな拡張や実験を行うことができます。例えば、以下のような拡張が考えられます：

1. 新しい料理レシピ（例：カレー、ステーキなど）の追加
2. 調理時間や難易度の概念の導入
3. 材料の量や調理器具の管理機能の追加
4. ユーザー入力による対話的な調理シミュレーションの実装

是非、自分なりのアレンジを加えて、Template Methodパターンの理解を深めるとともに、プログラミングの楽しさを味わってみてください。

この記事を通じて、Template Methodパターンの概念と実践的な使用方法について理解を深めていただけたら幸いです。コードの構造化と再利用性の向上に、このパターンを活用してみてください。