当前位置: 首页 > ops >正文

模板方法设计模式

模板方法设计模式是一种行为设计模式,它定义 了基类中算法的骨架,但允许子类在不改变其整体结构的情况下覆盖算法的特定步骤。

 

它在以下情况下特别有用:
您有一个明确定义的执行任务的步骤序列。
流程的某些部分在所有实现之间共享。
您希望允许子类自定义特定步骤,而无需重写整个算法。
当您要解决一个遵循通用高级结构但在几个步骤中略有变化的问题时,编写一个带有分支逻辑(如 OR 语句)的单个方法 来处理这些差异是很诱人的。if-elseswitch

例如,可以使用 条件以 CSV、JSON 或 XML 格式导出。DataExporterif
但随着新变体的加入,该方法变得臃肿、难以遵循,并且违反了单一责任原则和开放/封闭原则。

模板方法模式通过捕获基类中的常见工作流程 并将可自定义的步骤推送到子类中来解决这个问题,确保整体结构保持一致,同时在需要时提供灵活性。

让我们通过一个真实世界的示例,看看如何应用模板方法模式来构建灵活、可扩展和可重用的工作流。

问题:导出报告
假设您正在构建一个工具,允许您的应用程序以不同的格式导出报告,例如 CSV、PDF 和 Excel。

从表面上看,每个报表导出器都有不同的输出格式,但实际上,整个过程几乎相同。

 

以下是每个导出器遵循的高级工作流程:
准备数据:收集和组织要导出的数据。
打开文件:创建或打开所需格式的输出文件。
写入标题:输出列标题或元数据(特定于格式)。
写入数据行:迭代数据集并写入行(特定于格式)。
编写页脚:添加可选的摘要或页脚信息。
关闭文件:完成并关闭输出文件。

尽管有这些相似之处,但如果你天真地实现每个导出器,你可能会重复很多逻辑,这是有代价的。

报告数据

import java.util.List;
import java.util.Map;
import java.util.Arrays;public class ReportData {public List<String> getHeaders() {return Arrays.asList("ID", "Name", "Value");}public List<Map<String, Object>> getRows() {return Arrays.asList(Map.of("ID", 1, "Name", "Item A", "Value", 100.0),Map.of("ID", 2, "Name", "Item B", "Value", 150.5),Map.of("ID", 3, "Name", "Item C", "Value", 75.25));}    
}

CsvReportExporterNaive

class CsvReportExporterNaive {public void export(ReportData data, String filePath) {System.out.println("CSV Exporter: Preparing data (common)...");// ... data preparation logic ...System.out.println("CSV Exporter: Opening file '" + filePath + ".csv' (common)...");// ... file opening logic ...System.out.println("CSV Exporter: Writing CSV header (specific)...");// String.join(",", data.getHeaders());// ... write header to file ...System.out.println("CSV Exporter: Writing CSV data rows (specific)...");// for (Map<String, Object> row : data.getRows()) { ... format and write row ... }System.out.println("CSV Exporter: Writing CSV footer (if any) (common)...");System.out.println("CSV Exporter: Closing file '" + filePath + ".csv' (common)...");// ... file closing logic ...System.out.println("CSV Report exported to " + filePath + ".csv");}
}

PdfReportExporter天真

class PdfReportExporterNaive {public void export(ReportData data, String filePath) {System.out.println("PDF Exporter: Preparing data (common)...");// ... data preparation logic ...System.out.println("PDF Exporter: Opening file '" + filePath + ".pdf' (common)...");// ... PDF library specific file opening ...System.out.println("PDF Exporter: Writing PDF header (specific)...");// ... PDF library specific header writing ...System.out.println("PDF Exporter: Writing PDF data rows (specific)...");// ... PDF library specific data row writing ...System.out.println("PDF Exporter: Writing PDF footer (if any) (common)...");System.out.println("PDF Exporter: Closing file '" + filePath + ".pdf' (common)...");// ... PDF library specific file closing ...System.out.println("PDF Report exported to " + filePath + ".pdf");}
}

客户端代码

public class ReportAppNaive {public static void main(String[] args) {ReportData reportData = new ReportData();CsvReportExporterNaive csvExporter = new CsvReportExporterNaive();csvExporter.export(reportData, "sales_report");System.out.println();PdfReportExporterNaive pdfExporter = new PdfReportExporterNaive();pdfExporter.export(reportData, "financial_summary");}
}

这个设计有什么问题?
虽然这种方法乍一看似乎很简单,但它有几个严重的设计缺点:

代码重复
相同的步骤——准备数据、打开/关闭文件、编写页脚——在每个导出器类中重复。添加一个 ,您将第三次复制同一个样板。ExcelReportExporterNaive

维护开销
如果您决定更改数据的准备方式或文件的关闭方式(例如,添加日志记录或错误处理),则必须手动更新每个导出器类,从而增加引入错误的风险。

不一致的行为
由于每个导出器都自行实现整个工作流程,因此开发人员可能会面临以下实际危险:

省略一个步骤(例如,跳过页脚),
更改顺序(在标头之前写入数据),或
以不同的方式处理一个导出器的故障,而不是另一个出口商的故障。
这会导致逻辑不一致和代码脆弱。

可扩展性差
添加新的报告格式意味着复制一个完整的类,粘贴样板,并且只修改几行——违反了 DRY(不要重复自己)原则。

我们真正需要什么
我们需要一种方法来:
在基类中定义一次通用报表导出工作流。
允许子类仅覆盖特定于格式的步骤,例如编写标头和数据行。
确保所有报表导出器都遵循相同的顺序,并在算法结构中强制保持一致。
这正是模板方法模式的设计目的。

模板方法模式
模板方法模式在方法中定义算法的骨架,将一些步骤推迟到子类。它允许您保持流程的整体结构一致,同时为子类提供自定义算法特定部分的灵活性。

类图

 

关键组件:
AbstractClass(例如 AbstractReportExporter):
包含模板方法:一种 方法(例如,),用于定义算法的固定步骤顺序。finalexportReport()
声明必须由子类实现的一个或多个抽象方法(也称为原始作)。这些步骤可能因不同实现而异。

可能包括钩子方法:具有默认行为的具体方法,子类可以选择覆盖这些方法以自定义算法中的某些点。

具体类(例如, CsvReportExporterPdfReportExporter)
扩展抽象基类。
为模板中定义的抽象方法提供实现。
(可选)覆盖挂钩方法以自定义可选行为,例如添加页脚或格式样式。

现实世界的类比
想一想烤蛋糕的一般食谱(模板方法):
预热烤箱(常见步骤)。
准备面糊(此步骤各不相同 - 巧克力蛋糕、香草蛋糕等 - 抽象步骤)。
将面糊倒入锅中(常见步骤)。
烘烤 X 分钟(常见步骤,尽管 X 可能是一个钩子)。
放凉(常见步骤)。
给蛋糕上糖霜(可选步骤 - 钩法)。

整个烘焙过程由一般配方固定。特定类型的蛋糕(子类)只需要提供其独特的面糊准备,并且可能会选择覆盖糖霜步骤。

实现模板方法
让我们使用模板方法模式重构我们的报告导出系统。

目标是将通用报表生成工作流提取 到单个基类中,同时允许每个特定于格式的导出器仅自定义不同的部分。

  1. 1. 创建抽象基类 (AbstractReportExporter)
    此类包含模板方法,该方法定义了报告导出过程的固定结构。它还定义了抽象方法(由子类实现)和钩子方法(子类可以根据需要覆盖这些方法)。
public abstract class AbstractReportExporter {public final void exportReport(ReportData data, String filePath) {prepareData(data);openFile(filePath);writeHeader(data);writeDataRows(data);writeFooter(data);closeFile(filePath);System.out.println("Report exported to " + filePath);}// Hook method – optional for subclasses to overrideprotected void prepareData(ReportData data) {System.out.println("Preparing report data...");}// Hook method – optional for subclasses to overrideprotected void openFile(String filePath) {System.out.println("Opening file: " + filePath);}protected abstract void writeHeader(ReportData data);protected abstract void writeDataRows(ReportData data);// Hook method – optional for subclasses to overrideprotected void writeFooter(ReportData data) {System.out.println("Writing footer...");}// Hook method – optional for subclasses to overrideprotected void closeFile(String filePath) {System.out.println("Closing file: " + filePath);}
}
  1. 2. 实施混凝土出口商
    每个具体类都将扩展 和实现特定于格式的步骤。AbstractReportExporter

CSV 导出器

import java.util.Map;public class CsvReportExporter extends AbstractReportExporter {//prepareData() not overridden - default will be used//openFile() not overridden - default will be used@Overrideprotected void writeHeader(ReportData data) {System.out.println("CSV: Writing header: " + String.join(",", data.getHeaders()));}@Overrideprotected void writeDataRows(ReportData data) {System.out.println("CSV: Writing data rows...");for (Map<String, Object> row : data.getRows()) {System.out.println("CSV: " + row.values());}}// writeFooter() not overridden - default will be used// closeFile() not overridden - default will be used
}

PDF 导出器

import java.util.Map;public class PdfReportExporter extends AbstractReportExporter {//prepareData() not overridden - default will be used//openFile() not overridden - default will be used@Overrideprotected void writeHeader(ReportData data) {System.out.println("PDF: Writing header: " + String.join(",", data.getHeaders()));}@Overrideprotected void writeDataRows(ReportData data) {System.out.println("PDF: Writing data rows...");for (Map<String, Object> row : data.getRows()) {System.out.println("PDF: " + row.values());}}// writeFooter() not overridden - default will be used// closeFile() not overridden - default will be used
}

客户端代码使用抽象类
现在,客户端代码无需担心报表生成的内部步骤。它只是创建适当的导出器并调用该 方法。exportReport()

public class ReportAppTemplateMethod {public static void main(String[] args) {ReportData reportData = new ReportData();AbstractReportExporter csvExporter = new CsvReportExporter();csvExporter.exportReport(reportData, "sales_report");System.out.println();AbstractReportExporter pdfExporter = new PdfReportExporter();pdfExporter.exportReport(reportData, "financial_summary");}
}

使用模板方法模式,我们有:
通过将共享导出过程提取到基类中来消除代码重复。
通过强制执行相同的算法结构,确保所有导出器的一致性。
使系统具有可扩展性 — 添加新的导出器(例如 Excel)只需要创建一个新的子类。
改进的可维护性 — 通用逻辑仅在一个地方更改。
降低出错风险 — 步骤的顺序由抽象基类控制和保护。

http://www.xdnf.cn/news/18856.html

相关文章:

  • 【学习笔记】GB 42250-2022标准解析
  • 初始Linux——指令与权限
  • FPGA学习笔记——Verilog中可综合和不可综合语句
  • 2025软件测试面试八股文(完整版)
  • 【科研绘图系列】R语言在海洋生态学数据可视化中的应用:以浮游植物叶绿素和初级生产力为例
  • SFTP服务器可以通过同一个登录到SFTP服务器的账号密码连接上控制台吗
  • “上门经济”的胜利:深度解析家政O2O如何用“用户体验”重塑传统行业
  • 【小白笔记】网速
  • 支持向量机(SVM)学习总结
  • 德克西尔氢气探测器:工业安全守护核心
  • 从高层 PyTorch 到中层 CUDA Kernel 到底层硬件 Tensor Core
  • 深度解析BiTGAN:基于双向Transformer生成对抗网络的长期人体动作预测
  • Linux 把启动脚本制作成系统服务(通过 systemctl start xxx 启动)
  • JHipster-从零开始学习指南
  • Autodesk Maya 2026.2 全新功能详解:MotionMaker AI 动画、LookdevX 材质增强、USD 工作流优化
  • 实现自己的AI视频监控系统-第二章-AI分析模块3(核心)
  • Python常见设计模式3: 行为型模式
  • OpenCV4.X库功能全解---个人笔记
  • 【解锁Photonics for AI:系统学习光学神经网络与超表面设计,成就下一代光芯片工程师】
  • TCP并发服务器构建
  • Linux 离线环境下 Anaconda3 与核心机器学习库(scikit-learn/OpenCV/PyTorch)安装配置指南
  • React内网开发代理配置详解
  • 安装了TortoiseSVN但是在idea的subversion里面找不到svn.exe
  • LangChain4J-(3)-模型参数配置
  • 力扣 30 天 JavaScript 挑战 第41天 (第十二题)对异步操作,promise,async/await有了更深理解
  • 部署k8s-efk日志收集服务(小白的“升级打怪”成长之路)
  • 在 Ubuntu 系统上安装 MySQL
  • Spring Cloud 高频面试题详解(含代码示例与深度解析)
  • 浏览器与计算机网络
  • 计算机网络:服务器处理多客户端(并发服务器)