Menu
CoddyTech

模板方法模式

本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 78 课,共 91 课。

模板方法模式是一种行为型设计模式,它在基类中定义算法的框架,让子类可以重写特定步骤,而不改变整体结构。基类控制工作流程,子类则自定义各个部分。

考虑构建不同类型的文档。

每份文档都遵循相同的流程:创建页眉,添加内容,创建页脚。但每个步骤的具体实现会因文档类型而异。模板方法定义了这个顺序,而子类负责实现细节。

<?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

挑战

简单

让我们使用模板方法模式构建一个报告生成系统。不同类型的报告遵循相同的结构,包括标题部分、数据部分和摘要部分,但每种报告类型对这些部分的格式处理方式不同。模板方法将定义此工作流程,而子类可以自定义每个步骤。

你将把代码分成 4 个文件:

  • ReportGenerator.php:创建一个抽象的 ReportGenerator 类,用于定义报告生成算法的框架。你的类应当:
    • 在构造函数中接收报告标题(使用构造函数属性提升)
    • 包含一个 final 公共方法 generate(): string,按顺序调用 3 个步骤:createTitle()、createBody() 和 createSummary(),并拼接它们的结果
    • 声明 3 个抽象受保护方法,供子类实现:createTitle(): string、createBody(): string 和 createSummary(): string
  • HtmlReport.php:引入 ReportGenerator 文件,并创建一个继承它的 HtmlReport 类。此报告使用 HTML 格式输出:
    • createTitle() 返回 "<h1>[title]</h1>"
    • createBody() 返回 "<p>Report data goes here</p>"
    • createSummary() 返回 "<footer>End of report</footer>"
  • PlainTextReport.php:引入 ReportGenerator 文件,并创建一个继承它的 PlainTextReport 类。此报告使用简单的文本格式:
    • createTitle() 返回 "### [title] ###\n"
    • createBody() 返回 "Report data goes here\n"
    • createSummary() 返回 "--- End of report ---"
  • main.php:引入这两个报告文件。你将收到两个输入:报告类型("html" 或 "text")和报告标题。

    根据报告类型,使用给定的标题创建相应的报告生成器。调用 generate() 并打印完整报告。

模板方法模式确保所有报告都遵循相同的三步结构,同时允许每种报告类型以不同方式设置各部分的格式。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自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: PHP 在线编译器