Menu
CoddyTech

模板方法模式

本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 94 课,共 107 课。

模板方法模式在基类中定义算法的骨架,让子类可以重写特定步骤,而不改变整体结构。装饰器通过包装对象来添加行为,而模板方法则控制算法的流程,并允许自定义各个步骤。

在 Go 中,由于没有继承,我们使用结构体嵌入结合接口来实现这种模式。“模板”结构体定义算法的结构,并调用可自定义的方法:

type DataProcessor interface {
    ReadData() string
    ProcessData(data string) string
    SaveData(result string) string
}

type BaseProcessor struct {
    Impl DataProcessor
}

func (b *BaseProcessor) Execute() string {
    data := b.Impl.ReadData()
    result := b.Impl.ProcessData(data)
    return b.Impl.SaveData(result)
}

具体实现为每个步骤提供自己的版本,同时算法流程保持不变:

type CSVProcessor struct{}

func (c CSVProcessor) ReadData() string       { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string    { return "Saved: " + r }

type JSONProcessor struct{}

func (j JSONProcessor) ReadData() string       { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string    { return "Stored: " + r }

模板方法 Execute 按固定顺序编排各个步骤:

csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute())  // 已保存:processed-csv-data

jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // 已存储:parsed-json-data

当算法具有相同的结构,但具体步骤有所不同时,模板方法模式就很适合使用,例如数据导入/导出流程、报告生成器或测试框架。

challenge icon

挑战

简单

让我们使用模板方法模式来构建一个报告生成系统!你将创建一个框架,让不同类型的报告遵循相同的生成流程:收集数据、设置格式并输出结果,但每种报告类型都可以用不同的方式自定义这些步骤。

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

  • report.go:定义接口和负责协调报告生成算法的基础处理器。

    创建一个 ReportGenerator 接口,其中包含代表报告生成步骤的三个方法:

    • GatherData() string:获取报告的原始数据
    • FormatData(data string) string:将数据转换为报告格式
    • OutputReport(formatted string) string:生成最终输出消息

    构建一个持有 ReportGenerator 实现的 ReportProcessor 结构体。添加一个 Generate() string 方法,按顺序执行三个步骤:先收集数据,再设置格式,最后输出,并返回最终结果。

  • generators.go:实现具体的报告生成器,自定义每个步骤。

    创建两种报告类型:

    • SalesReport,包含一个 Region 字段(字符串)
      • GatherData() 返回 sales-data-[region]
      • FormatData(data) 返回 SALES REPORT: [data]
      • OutputReport(formatted) 返回 Printed: [formatted]
    • InventoryReport,包含一个 Warehouse 字段(字符串)
      • GatherData() 返回 inventory-[warehouse]
      • FormatData(data) 返回 *** [data] ***
      • OutputReport(formatted) 返回 Exported: [formatted]
  • main.go:演示相同的算法结构如何根据具体实现生成不同的结果。

    读取报告类型(sales 或 inventory)和配置值(销售报告对应区域,库存报告对应仓库)。创建相应的生成器,将其封装在 ReportProcessor 中,调用 Generate(),并打印结果。

输入如下:

  • 第 1 行:报告类型(sales 或 inventory)
  • 第 2 行:配置值(区域名称或仓库名称)

例如,给定:

sales
North

你的输出应为:

Printed: SALES REPORT: sales-data-North

再例如,给定:

inventory
MainHub

你的输出应为:

Exported: *** inventory-MainHub ***

再例如,给定:

sales
West

你的输出应为:

Printed: SALES REPORT: sales-data-West

注意,ReportProcessor 始终按相同顺序调用相同的三个步骤,但每种报告类型都会为这些步骤提供自己的实现。算法的框架保持不变,具体细节则有所不同:这就是模板方法模式!

亲手试试

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// 读取报表类型
	scanner.Scan()
	reportType := scanner.Text()
	
	// 读取配置值(区域或仓库)
	scanner.Scan()
	configValue := scanner.Text()

	// TODO: 根据 reportType 创建合适的生成器
	// - 如果 reportType 是 "sales",创建 SalesReport,并将 Region 设置为 configValue
	// - 如果 reportType 是 "inventory",创建 InventoryReport,并将 Warehouse 设置为 configValue
	
	// TODO: 使用生成器创建 ReportProcessor
	
	// TODO: 调用 Generate() 并打印结果
	_ = reportType
	_ = configValue
	fmt.Println("TODO: Generate and print the report")
}
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Go 在线编译器