Menu
Coddy logo textTech

テンプレートメソッドパターン

CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 78/91。

Template Method Patternは、アルゴリズムの骨組みを基底クラスで定義し、全体の構造を変えることなく、特定のステップをサブクラスでオーバーライドできるようにする振る舞いに関するデザインパターンです。基底クラスがワークフローを制御し、サブクラスが個々の部分をカスタマイズします。

さまざまな種類のドキュメントの作成を検討してみましょう。

すべてのドキュメントは同じプロセスに従います:ヘッダーの作成、コンテンツの追加、フッターの作成。しかし、各ステップの詳細はドキュメントの種類によって異なります。テンプレートメソッドはこの一連の流れを定義し、サブクラスがその詳細を実装します。

<?php
abstract class DocumentGenerator {
    // テンプレートメソッド - アルゴリズムの構造を定義します
    public final function generate(): string {
        $output = $this->createHeader();
        $output .= $this->createContent();
        $output .= $this->createFooter();
        return $output;
    }
    
    // サブクラスで実装されるべきステップ
    abstract protected function createHeader(): string;
    abstract protected function createContent(): string;
    abstract protected function createFooter(): string;
}

class HtmlDocument extends DocumentGenerator {
    protected function createHeader(): string {
        return "<html><head></head><body>";
    }
    
    protected function createContent(): string {
        return "<p>Hello World</p>";
    }
    
    protected function createFooter(): string {
        return "</body></html>";
    }
}

class TextDocument extends DocumentGenerator {
    protected function createHeader(): string {
        return "=== DOCUMENT ===\n";
    }
    
    protected function createContent(): string {
        return "Hello World\n";
    }
    
    protected function createFooter(): string {
        return "=== END ===";
    }
}

$html = new HtmlDocument();
echo $html->generate() . "\n";

$text = new TextDocument();
echo $text->generate();

出力:

<html><head></head><body><p>Hello World</p></body></html>
=== DOCUMENT ===
Hello World
=== END ===

テンプレートメソッドにある final キーワードに注目してください。これにより、サブクラスがアルゴリズムの構造を変更することを防ぎます。このパターンは、同じワークフローに従いつつも実装の詳細が異なる複数のクラスがある場合に最適です。新しいドキュメントタイプを追加するということは、既存のコードに手を加えることなく、新しいサブクラスを作成することを意味します。

challenge icon

チャレンジ

簡単

Template Method パターンを使用して、レポート生成システムを構築しましょう。異なる種類のレポートであっても、タイトルセクション、データセクション、サマリーセクションという同じ構造に従いますが、レポートの種類ごとにこれらのセクションのフォーマットが異なります。テンプレートメソッドはこのワークフローを定義し、サブクラスが各ステップをカスタマイズします。

コードは4つのファイルに分けて構成します:

  • ReportGenerator.php — レポート生成アルゴリズムのスケルトンを定義する抽象クラス ReportGenerator を作成します。このクラスは以下の要件を満たす必要があります:
    • コンストラクタでレポートのタイトルを受け取る(コンストラクタのプロパティ昇格を使用してください)
    • final な public メソッド generate(): string を持ち、createTitle()createBody()createSummary() の3つのステップを順番に呼び出し、それらの結果を連結して返す
    • サブクラスが実装しなければならない3つの抽象 protected メソッド createTitle(): stringcreateBody(): stringcreateSummary(): string を宣言する
  • HtmlReport.php — ReportGenerator ファイルを読み込み、それを継承した HtmlReport クラスを作成します。このレポートは出力を HTML としてフォーマットします:
    • createTitle()"<h1>" . $this->title . "</h1>" を返します("<h1>[title]</h1>" 形式)
    • createBody()"<p>Report data goes here</p>" を返します
    • createSummary()"<footer>End of report</footer>" を返します
  • PlainTextReport.php — ReportGenerator ファイルを読み込み、それを継承した PlainTextReport クラスを作成します。このレポートはシンプルなテキスト形式を使用します:
    • createTitle()"### " . $this->title . " ###\n" を返します("### [title] ###\n" 形式)
    • createBody()"Report data goes here\n" を返します
    • createSummary()"--- End of report ---" を返します
  • main.php — 両方のレポートファイルを読み込みます。2つの入力、レポートタイプ("html" または "text")とレポートタイトルを受け取ります。

    レポートタイプに基づいて、指定されたタイトルで適切なレポートジェネレータを作成します。generate() を呼び出し、完成したレポートを出力してください。

Template Method パターンは、すべてのレポートが同じ3ステップの構造に従うことを保証しつつ、各レポートタイプがセクションを独自にフォーマットできるようにします。generate() メソッドに final キーワードを付けることで、サブクラスがアルゴリズムの順序を変更することを防ぎ、個々のステップのみをカスタマイズできるように制限します。

チートシート

Template Method パターンは、アルゴリズムのスケルトンを基底クラスで定義し、全体の構造を変えることなく、サブクラスが特定のステップをオーバーライドできるようにします。

基底クラスには、ワークフローのシーケンスを制御するテンプレートメソッドが含まれています。このメソッドは通常、サブクラスがアルゴリズムの構造を変更するのを防ぐために final としてマークされます。

サブクラスは抽象メソッドを実装して、アルゴリズムの個々のステップをカスタマイズします。

<?php
abstract class DocumentGenerator {
    // テンプレートメソッド - アルゴリズムの構造を定義
    public final function generate(): string {
        $output = $this->createHeader();
        $output .= $this->createContent();
        $output .= $this->createFooter();
        return $output;
    }
    
    // サブクラスが実装する抽象ステップ
    abstract protected function createHeader(): string;
    abstract protected function createContent(): string;
    abstract protected function createFooter(): string;
}

class HtmlDocument extends DocumentGenerator {
    protected function createHeader(): string {
        return "<html><head></head><body>";
    }
    
    protected function createContent(): string {
        return "<p>Hello World</p>";
    }
    
    protected function createFooter(): string {
        return "</body></html>";
    }
}

$html = new HtmlDocument();
echo $html->generate();

主な特徴:

  • テンプレートメソッドは、アルゴリズム構造の変更を防ぐために final とマークされます
  • 抽象メソッドは、サブクラスのカスタマイズポイントを定義します
  • 複数のクラスが同じワークフローに従うが、実装の詳細が異なる場合に最適です
  • 新しいバリアントを追加するには、既存のコードを変更せずに新しいサブクラスを作成するだけで済みます

自分で試してみよう

<?php

require_once 'HtmlReport.php';
require_once 'PlainTextReport.php';

// 入力を読み込む
$reportType = trim(fgets(STDIN));
$reportTitle = trim(fgets(STDIN));

// TODO: レポートの種類("html" または "text")に基づいて、
// 指定されたタイトルで適切なレポートジェネレータを作成する

// TODO: generate() を呼び出し、完全なレポートを出力する

?>
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン