行为设计模式之Template Method(模板方法)

发布于:2025-06-18 ⋅ 阅读:(15) ⋅ 点赞:(0)

行为设计模式之Template Method(模板方法)

摘要:
模板方法模式是一种行为设计模式,通过定义算法骨架并将特定步骤延迟到子类实现,实现代码复用和扩展控制。该模式包含抽象类(定义原语操作和模板方法)和具体类(实现特定步骤)。适用于固定流程但需灵活步骤的场景,如框架开发、GUI工具包和数据处理。示例展示了订单处理中用户、商家和快递服务的差异化实现。该模式能消除重复代码,严格管控扩展点,并通过钩子方法提供灵活性,常见于Spring、JUnit等框架中。

1)意图

定义一个操作中的算法骨架,而将一些步骤延迟到子类中。Template Method 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

2)结构

在这里插入图片描述
其中:

  • AbstractClass(抽象类)定义抽象的原语操作,具体的子类将重定义它们以实现一个算法的各步骤;实现模版方法,定义一个算法的骨架,该模版方法不仅调用原语操作,也调用定义在AbstractClass 或其他对象中的操作。
  • ConcreteClass (具体类) 实现原语操作以完成算法中与特定子类相关的步骤。

3)适用性

  • 一次性实现一个算法的不变的部分,并将可变的行为留给子类实现。
  • 各子类中公共的行为应被提取出来并集中到一个公共父类中,以避免代码重复。
  • 控制子类扩展。模板方法旨在调用“hook”操作(默认的行为,子类可以在必要时进行重定义扩展),这就只允许在这些点进行扩展。
/**
 * @author psd 行为设计模式之模板方法模式
 */
public class TemplateMethodPattern {
    public static void main(String[] args) {
        OrderProcessingTemplate user = new UserOrderProcessingTemplate();
        user.templateMethod();
        System.out.println("--------------------------------------------------");
        OrderProcessingTemplate shop = new ShopOrderProcessingTemplate();
        shop.templateMethod();
        System.out.println("--------------------------------------------------");
        OrderProcessingTemplate express = new ExpressDeliveryProcessingTemplate();
        express.templateMethod();
    }
}


abstract class OrderProcessingTemplate {
    public void templateMethod(){
        System.out.println("订单 创建创建前");
        primitiveOperation1();
        System.out.println("订单 创建创建后");
        primitiveOperation2();
    }

    /**
     * 抽象方法1
     *      用户创建订单前 预览商品、选定商品
     *      商家创建订单前 填写商品卖点,上架商品
     */
    public abstract void primitiveOperation1();

    /**
     *抽象方法2
     *      用户创建订单后 填写订单信息,确认订单 等待发货
     *      商家创建订单后 填写商品信息,确认订单 发货
     */
    public abstract void primitiveOperation2();
}

/**
 * 用户
 */
class UserOrderProcessingTemplate extends OrderProcessingTemplate {
    @Override
    public void primitiveOperation1() {
        System.out.println("用户创建订单前 预览商品、选定商品");
    }

    @Override
    public void primitiveOperation2() {
        System.out.println("用户创建订单后 填写订单信息,确认订单 等待发货");
    }
}

/**
 * 商家
 */
class ShopOrderProcessingTemplate extends OrderProcessingTemplate {
    @Override
    public void primitiveOperation1() {
        System.out.println("商家创建订单前 填写商品卖点,上架商品");
    }

    @Override
    public void primitiveOperation2() {
        System.out.println("商家创建订单后 填写商品信息,确认订单 发货");
    }
}

/**
 * 快递服务
 */
class ExpressDeliveryProcessingTemplate extends OrderProcessingTemplate {

    @Override
    public void primitiveOperation1() {
        System.out.println("快递服务:创建订单前,与商家确认好发货单价");
    }

    @Override
    public void primitiveOperation2() {
        System.out.println("快递服务:创建订单后,与商家确认好收货地址");
    }
}

运行结果:
在这里插入图片描述

4)以下是模板方法模式的典型使用场景:

4.1 固定算法流程,允许特定步骤灵活实现:

当你有一个由多个步骤组成的算法或流程,其执行顺序是固定的,但其中某些步骤的具体实现可能因情况而异时。

例子:

文档处理: 生成报告的过程可能是:打开文档模板 -> 填充数据 -> 应用样式 -> 保存文档。填充数据和应用样式的具体方式(如填充数据库数据还是API数据,应用公司A风格还是公司B风格)可能不同,但步骤顺序固定。

构建流程: 软件构建流程可能是:拉取代码 -> 编译 -> 运行测试 -> 打包 -> 部署。编译(用GCC还是Clang)、运行测试(单元测试还是集成测试套件)、部署(部署到测试环境还是生产环境)的具体命令或目标可能不同。

数据解析/导入: 解析文件导入数据的流程:打开文件 -> 解析文件内容 -> 验证数据 -> 持久化到数据库 -> 关闭文件。解析文件内容(CSV解析器 vs JSON解析器)和持久化到数据库(插入新记录 vs 更新已有记录)的具体逻辑会变化。

4.2 在框架或基础库中定义通用处理流程:

框架设计者希望提供一套通用的处理逻辑骨架,同时允许框架使用者(子类)定制化其中的关键部分,以满足具体应用需求。这是模板方法模式最强大的应用场景之一。

例子:

Web框架请求处理: 一个Servlet或Controller处理HTTP请求的骨架可能是:解析请求参数 -> 权限校验 -> 执行业务逻辑 -> 处理异常 -> 渲染响应。框架定义了步骤顺序和默认实现(如通用的异常处理、响应渲染),开发者只需重写执行业务逻辑等方法。

游戏引擎生命周期: 游戏对象(如敌人、道具)的生命周期管理:初始化 -> 更新状态 -> 渲染 -> 销毁。引擎定义了这个调用顺序,开发者继承基类并实现具体的更新状态(敌人AI逻辑)和渲染(绘制模型)逻辑。

单元测试框架: 测试用例的执行流程:setUp(准备测试环境) -> runTest(执行测试方法) -> tearDown(清理测试环境)。框架定义了流程,测试编写者只需实现runTest方法包含具体的测试断言。

4.3 消除子类中的重复代码(算法步骤的复用):

当多个相关类中包含相同顺序的操作步骤,但每个类在这些步骤的实现上有所不同时,将这些共同步骤提升到抽象基类的模板方法中,可以避免在每个子类中重复编写控制流程的代码,只让子类负责差异化的具体实现。

价值: 符合“Don’t Repeat Yourself (DRY)”原则,提高了代码复用性,使公共流程的修改变得集中(只需修改基类)。

4.4 需要严格控制子类的扩展点:

基类设计者希望明确规定哪些部分允许子类定制(通过声明为抽象方法或提供可覆盖的钩子方法),哪些部分(算法骨架/流程)是固定的、不允许子类修改的(通常将模板方法声明为final)。

价值: 保证了核心算法流程的一致性和正确性,防止子类意外破坏关键流程。

4.5 利用“钩子方法”(Hook Methods)提供额外扩展性:

除了必须实现的抽象步骤外,模板方法模式常常在算法骨架的关键点定义一些有默认实现(通常是空实现或返回默认值)的“钩子方法”。子类可以选择性地覆盖这些钩子方法,以在特定点插入额外的逻辑,从而对算法流程进行更精细的干预,而不必强制重写主要步骤。

例子: 在文档处理流程的保存文档步骤之前,可能有一个beforeSave()钩子方法,子类可以覆盖它来添加水印或进行最终校验。

5)常见应用领域举例:

5.1 框架开发:

Spring Framework: JdbcTemplate 的 execute(), query(), update() 等方法定义了操作JDBC的核心流程(获取连接、创建语句、执行SQL、处理结果集、处理异常、释放资源),具体的SQL执行和结果集处理通过回调接口(如 PreparedStatementCreator, RowMapper)由使用者提供,这本质上是模板方法模式的一种变体(常称为回调模式)。

JUnit: TestCase 类的 runBare() 方法(setUp -> runTest -> tearDown)。

Java Servlet API: HttpServlet 的 service() 方法根据HTTP方法(GET, POST等)调用相应的 doGet(), doPost() 等方法。service() 是模板方法,doXxx() 是子类需要实现的步骤。

5.2 GUI 工具包和应用程序:

窗口或对话框的打开/关闭流程:preOpen() -> createContents() -> postOpen() / preClose() -> validateInput() -> saveData() -> postClose()。createContents() 和 saveData() 通常需要子类实现。

游戏或动画中的每一帧渲染循环。

5.3 数据处理与报告生成:

数据ETL(抽取、转换、加载)流程。

不同格式(PDF, HTML, CSV)报告的生成流程,共用数据准备和获取逻辑,差异化渲染步骤。

5.4 算法实现:

Java集合框架中的 AbstractList, AbstractSet, AbstractMap 等类提供了集合操作的骨架实现(如 indexOf 基于迭代器实现),子类只需实现核心方法(如 get, size, 迭代器)。

排序算法如果需要不同的比较逻辑(但排序步骤相同),可以用模板方法定义排序过程,抽象出比较步骤(但这通常更适合用策略模式)。

5.5 生命周期管理:

如前面提到的游戏对象生命周期、Android Activity/Fragment 的生命周期方法 (onCreate, onStart, onResume, onPause, onStop, onDestroy) 由系统框架按固定顺序调用,开发者实现具体逻辑。

资源(数据库连接、网络连接、文件句柄)的获取、使用、释放流程。

5.6 业务工作流:

订单处理、审批流程、客户服务工单处理等具有固定阶段序列的业务流程,其中某些阶段的处理逻辑需要定制。

总结关键点(何时使用模板方法模式):

流程固定,步骤可变: 算法或操作有清晰、固定的步骤顺序**,但部分步骤的具体实现需要变化或扩展。**

抽取公共骨架: 多个子类存在相同顺序的操作流程,需要在父类中集中复用流程代码,避免重复。

框架定义流程,子类填充细节: 作为框架或基础库设计者,你想定义核心处理流程,并开放特定步骤供使用者实现。

控制扩展点: 你需要明确规定哪些部分允许子类覆盖(抽象方法/钩子),哪些核心流程禁止子类修改(final 模板方法)。

提供可选钩子: 除了强制实现的步骤,你还想提供可选覆盖点(钩子方法)供子类插入额外逻辑。

喜欢我的文章记得点个在看,或者点赞,持续更新中ing…


网站公告

今日签到

点亮在社区的每一天
去签到