Template Method パターン

分類: 振る舞いパターン

目的: アルゴリズムの骨格を定義し、一部のステップをサブクラスに延期します。アルゴリズムの構造を変えずに特定のステップを再定義できます。

概要

Template Method パターンは、アルゴリズムの構造を抽象クラスで定義し、一部の処理をサブクラスで実装できるようにするデザインパターンです。これにより、共通のプロセスを再利用しながら、特定のステップをカスタマイズすることができます。

クラス図

図1: Template Method パターンのクラス図

主要なコンポーネント

  • AbstractClass(抽象クラス):テンプレートメソッドを定義し、アルゴリズムの骨格を実装します。また、サブクラスが実装すべき抽象メソッドやフックメソッドを宣言します。
  • ConcreteClass(具象クラス):AbstractClass で定義された抽象メソッドを実装し、必要に応じてフックメソッドをオーバーライドします。

シーケンス図

図2: Template Method パターンのシーケンス図

実装例

AbstractClass(ドキュメント処理の例)

public abstract class DocumentProcessor {
    // テンプレートメソッド
    public final void processDocument() {
        openDocument();
        extractContent();
        processContent();
        generateReport();
        saveDocument();
        
        if (shouldNotifyUser()) {
            notifyUser();
        }
    }
    
    // サブクラスで実装が必要なメソッド
    protected abstract void extractContent();
    protected abstract void processContent();
    
    // デフォルト実装を持つメソッド
    protected void openDocument() {
        System.out.println("ドキュメントを開いています...");
    }
    
    protected void generateReport() {
        System.out.println("レポートを生成しています...");
    }
    
    protected void saveDocument() {
        System.out.println("ドキュメントを保存しています...");
    }
    
    // フックメソッド
    protected boolean shouldNotifyUser() {
        return true;
    }
    
    private void notifyUser() {
        System.out.println("ユーザーに処理完了を通知しています...");
    }
}

ConcreteClass(PDF処理)

public class PDFProcessor extends DocumentProcessor {
    @Override
    protected void extractContent() {
        System.out.println("PDFからテキストとイメージを抽出しています...");
    }
    
    @Override
    protected void processContent() {
        System.out.println("PDFコンテンツを処理しています...");
        System.out.println("テキスト解析と検索インデックスの作成...");
    }
    
    @Override
    protected void saveDocument() {
        System.out.println("処理済みPDFを保存しています...");
    }
}

ConcreteClass(Word処理)

public class WordProcessor extends DocumentProcessor {
    @Override
    protected void extractContent() {
        System.out.println("Word文書からテキストと書式を抽出しています...");
    }
    
    @Override
    protected void processContent() {
        System.out.println("Word文書のコンテンツを処理しています...");
        System.out.println("スタイルの標準化と見出しの抽出...");
    }
    
    // フックメソッドのオーバーライド
    @Override
    protected boolean shouldNotifyUser() {
        return false; // ユーザー通知を無効化
    }
}

クライアント

public class DocumentProcessingExample {
    public static void main(String[] args) {
        System.out.println("PDF処理の開始:");
        DocumentProcessor pdfProcessor = new PDFProcessor();
        pdfProcessor.processDocument();
        
        System.out.println("\nWord文書処理の開始:");
        DocumentProcessor wordProcessor = new WordProcessor();
        wordProcessor.processDocument();
    }
}

使用例

  • フレームワーク:定義された順序でコールバックメソッドを呼び出す
  • データ処理パイプライン:データの読み込み、変換、処理、出力の流れを定義
  • ライフサイクルメソッド:初期化、実行、クリーンアップなどの定義されたライフサイクル
  • Java の抽象クラス:java.io.InputStream、java.io.OutputStream など
  • JUnit テストフレームワーク:setUp()、tearDown() などのライフサイクルメソッド

メリット

  • コードの再利用:共通のアルゴリズム構造を一度定義するだけで良い
  • 制御の反転:親クラスがアルゴリズムの流れを制御し、サブクラスが特定の部分を実装する
  • 拡張性:新しい実装を追加する際に既存のコードを変更せずに済む
  • 変更の局所化:アルゴリズムの共通部分の変更が一箇所で済む

デメリット

  • 制約の多さ:サブクラスがアルゴリズムの構造を変更できない
  • 継承の問題:継承を使用するため、クラス階層が複雑になる可能性がある
  • リスコフの置換原則違反:注意深く設計しないと、サブクラスが親クラスの契約を破る可能性がある

関連パターン

  • Strategy パターン:Template Method が継承を使用するのに対し、Strategy は委譲を使用する
  • Factory Method パターン:しばしば Template Method の一部として使用される