0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

全30回:静的と動的でどう違うのか、JavaとPythonで学ぶデザインパターン - Day 24 Template Methodパターン:アルゴリズムの骨格を定義する

Posted at

はじめに

皆さん、こんにちは!「JavaとPythonで比べるデザインパターン」シリーズの第24回目です。

今回は、アルゴリズムの骨格(テンプレート)を抽象クラスで定義し、特定のステップをサブクラスに委譲するTemplate Method(テンプレートメソッド)パターンについて解説します。

Template Methodパターンとは?

Template Methodパターンは、ある処理の全体的な流れ(アルゴリズムの骨格)を親クラスで定義し、その流れの中の具体的なステップをサブクラスに実装させる振る舞いパターンです。

身近な例で理解する

パン作りのレシピを考えてみましょう。

どんなパンでも基本的な工程は共通です:

  1. 材料を準備する
  2. 生地をこねる
  3. 発酵させる
  4. 形を整える
  5. 焼く

しかし、パンの種類(食パン、クロワッサン、フランスパンなど)によって、具体的な作業内容は異なります。例えば:

  • こね方:食パンは機械でこねるが、フランスパンは手でこねる
  • 発酵時間:種類によって異なる
  • 焼く温度:パンごとに最適温度が違う

Template Methodパターンでは、この共通の工程順序を親クラスで定義し、具体的な作業内容を子クラスに委譲します。

パターンの目的とメリット

1. アルゴリズムの再利用
共通のアルゴリズム構造を一箇所で管理し、重複コードを排除

2. 拡張性の確保
新しいバリエーションを追加する際、既存の骨格を再利用

3. 制御の反転(IoC)
「何をするか」は子クラスが決め、「いつするか」は親クラスが制御

4. オープン・クローズド原則の実現
拡張に対してオープン、修正に対してクローズド

Javaでの実装:型安全性と厳格な制御

Javaでは抽象クラスとfinalメソッドを使って、アルゴリズムの流れを厳格に制御できます。

基本構造

// 抽象クラス:アルゴリズムの骨格を定義
abstract class CaffeineBeverage {
    
    // テンプレートメソッド:アルゴリズムの骨格(finalで変更不可)
    public final void prepareBeverage() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
        cleanup();
    }
    
    // 抽象メソッド:サブクラスが必ず実装
    protected abstract void brew();
    protected abstract void addCondiments();
    
    // フック(オプショナルな実装)
    protected boolean customerWantsCondiments() {
        return true; // デフォルトは追加する
    }
    
    // 共通処理
    private void boilWater() {
        System.out.println("お湯を沸かしています");
    }
    
    private void pourInCup() {
        System.out.println("カップに注いでいます");
    }
    
    private void cleanup() {
        System.out.println("後片付けをしています");
    }
}

// 具象クラス:コーヒーの具体的な実装
class Coffee extends CaffeineBeverage {
    @Override
    protected void brew() {
        System.out.println("フィルターでコーヒーを淹れています");
    }
    
    @Override
    protected void addCondiments() {
        System.out.println("砂糖とミルクを追加しています");
    }
    
    @Override
    protected boolean customerWantsCondiments() {
        return askForCondiments("砂糖とミルク");
    }
    
    private boolean askForCondiments(String condiment) {
        // 実際のアプリケーションでは、ユーザー入力を取得
        System.out.print(condiment + "を追加しますか? (y/n): ");
        // 簡略化のため、常にtrueを返す
        return true;
    }
}

// 具象クラス:紅茶の具体的な実装
class Tea extends CaffeineBeverage {
    @Override
    protected void brew() {
        System.out.println("ティーバッグを浸しています");
    }
    
    @Override
    protected void addCondiments() {
        System.out.println("レモンを追加しています");
    }
    
    @Override
    protected boolean customerWantsCondiments() {
        return askForCondiments("レモン");
    }
    
    private boolean askForCondiments(String condiment) {
        System.out.print(condiment + "を追加しますか? (y/n): ");
        return true;
    }
}

// 使用例
public class BeverageTest {
    public static void main(String[] args) {
        System.out.println("=== コーヒーを作ります ===");
        CaffeineBeverage coffee = new Coffee();
        coffee.prepareBeverage();
        
        System.out.println("\n=== 紅茶を作ります ===");
        CaffeineBeverage tea = new Tea();
        tea.prepareBeverage();
    }
}

Javaの特徴

  • finalメソッド:テンプレートメソッドの変更を防止
  • 抽象メソッド:サブクラスでの実装を強制
  • アクセス修飾子:適切な情報隠蔽
  • フックメソッド:オプショナルなカスタマイゼーション

Pythonでの実装:柔軟性と簡潔性

Pythonではabcモジュールを使用して抽象クラスを定義し、より柔軟な実装が可能です。

from abc import ABC, abstractmethod
from typing import Optional

# 抽象クラス:アルゴリズムの骨格を定義
class CaffeineBeverage(ABC):
    
    # テンプレートメソッド:アルゴリズムの骨格
    def prepare_beverage(self) -> None:
        """飲み物を準備するメインプロセス"""
        self._boil_water()
        self._brew()
        self._pour_in_cup()
        if self._customer_wants_condiments():
            self._add_condiments()
        self._cleanup()
    
    # 抽象メソッド:サブクラスが必ず実装
    @abstractmethod
    def _brew(self) -> None:
        """抽出方法を定義"""
        pass
    
    @abstractmethod
    def _add_condiments(self) -> None:
        """調味料の追加方法を定義"""
        pass
    
    # フック:オプショナルな実装(オーバーライド可能)
    def _customer_wants_condiments(self) -> bool:
        """調味料を追加するかのフック"""
        return True
    
    # 共通処理(プライベートメソッド風)
    def _boil_water(self) -> None:
        print("お湯を沸かしています")
    
    def _pour_in_cup(self) -> None:
        print("カップに注いでいます")
    
    def _cleanup(self) -> None:
        print("後片付けをしています")

# 具象クラス:コーヒーの実装
class Coffee(CaffeineBeverage):
    
    def __init__(self, with_condiments: Optional[bool] = None):
        self._with_condiments = with_condiments
    
    def _brew(self) -> None:
        print("フィルターでコーヒーを淹れています")
    
    def _add_condiments(self) -> None:
        print("砂糖とミルクを追加しています")
    
    def _customer_wants_condiments(self) -> bool:
        if self._with_condiments is not None:
            return self._with_condiments
        return self._ask_for_condiments("砂糖とミルク")
    
    def _ask_for_condiments(self, condiment: str) -> bool:
        # 実際のアプリケーションでは入力を取得
        print(f"{condiment}を追加しますか? (y/n): ", end="")
        return True  # 簡略化のため常にTrue

# 具象クラス:紅茶の実装
class Tea(CaffeineBeverage):
    
    def __init__(self, with_condiments: Optional[bool] = None):
        self._with_condiments = with_condiments
    
    def _brew(self) -> None:
        print("ティーバッグを浸しています")
    
    def _add_condiments(self) -> None:
        print("レモンを追加しています")
    
    def _customer_wants_condiments(self) -> bool:
        if self._with_condiments is not None:
            return self._with_condiments
        return self._ask_for_condiments("レモン")
    
    def _ask_for_condiments(self, condiment: str) -> bool:
        print(f"{condiment}を追加しますか? (y/n): ", end="")
        return True

# 関数型アプローチの例
def create_beverage_maker(brew_func, condiment_func, wants_condiments=True):
    """関数を使ってテンプレートメソッドパターンを実現"""
    
    def prepare_beverage():
        print("お湯を沸かしています")
        brew_func()
        print("カップに注いでいます")
        if wants_condiments:
            condiment_func()
        print("後片付けをしています")
    
    return prepare_beverage

# 使用例
if __name__ == "__main__":
    print("=== コーヒーを作ります ===")
    coffee = Coffee(with_condiments=True)
    coffee.prepare_beverage()
    
    print("\n=== 紅茶を作ります(調味料なし) ===")
    tea = Tea(with_condiments=False)
    tea.prepare_beverage()
    
    print("\n=== 関数型アプローチ ===")
    # 関数を使った実装例
    espresso_maker = create_beverage_maker(
        brew_func=lambda: print("エスプレッソマシンで抽出しています"),
        condiment_func=lambda: print("泡立てたミルクを追加しています"),
        wants_condiments=True
    )
    espresso_maker()

Pythonの特徴

  • @abstractmethod:抽象メソッドの定義
  • 柔軟なフック実装:コンストラクタでの設定が可能
  • 関数型アプローチ:関数を組み合わせた代替実装
  • 型ヒント:コードの可読性向上

実践的な応用例

フレームワークでの活用

// Webフレームワークでのリクエスト処理
abstract class RequestHandler {
    public final void handleRequest(HttpRequest request) {
        if (!authenticate(request)) {
            sendUnauthorized();
            return;
        }
        
        validateInput(request);
        Object result = processRequest(request);
        sendResponse(result);
        logRequest(request, result);
    }
    
    protected abstract Object processRequest(HttpRequest request);
    
    // フック:必要に応じてオーバーライド
    protected boolean authenticate(HttpRequest request) {
        return true; // デフォルトは認証OK
    }
    
    protected void validateInput(HttpRequest request) {
        // 基本的なバリデーション
    }
}

まとめ:アルゴリズムの構造化と再利用

観点 Java Python
アルゴリズム制御 finalメソッドで厳格に固定 慣習的な保護(アンダースコア)
抽象化 abstractキーワード @abstractmethodデコレータ
拡張性 継承ベースの厳密な構造 継承+関数型の柔軟なアプローチ
型安全性 コンパイル時チェック 実行時チェック+型ヒント

Template Methodパターンの適用場面

✅ 適している場面

  • フレームワークやライブラリの設計
  • データ処理のパイプライン
  • テストフレームワーク
  • ゲームのAIルーチン

❌ 避けるべき場面

  • アルゴリズムが頻繁に変更される
  • 処理ステップが大幅に異なる
  • 継承よりコンポジションが適している

Template Methodパターンは、アルゴリズムの共通構造を抽出し、変動部分を適切に分離することで、保守性と拡張性を両立させる強力な設計パターンです。

次回はいよいよ最終回!振る舞いパターンの締めくくりとして、Visitorパターンについて解説します。オブジェクトの構造からアルゴリズムを分離し、新しい操作を既存のクラス階層に影響を与えずに追加できる、高度で興味深いパターンです。


次回予告:「Day 25 Visitorパターン:オブジェクト構造からアルゴリズムを分離する」

#Java #Python #デザインパターン #TemplateMethod #オブジェクト指向 #継承 #抽象クラス #フレームワーク設計

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?