【西瓜带你学设计模式 | 第十一期 - 模板方法模式】模板方法模式 —— 流程骨架与钩子实现、优缺点与适用场景

文章目录

    • 前言
    • [1. 模板方法模式是什么?](#1. 模板方法模式是什么?)
    • [2. 模板方法模式解决什么问题?](#2. 模板方法模式解决什么问题?)
    • [3. 实现步骤](#3. 实现步骤)
    • [4. 静态结构示例](#4. 静态结构示例)
      • [4.1 抽象模板类:定义骨架流程](#4.1 抽象模板类:定义骨架流程)
      • [4.2 子类:实现可变步骤](#4.2 子类:实现可变步骤)
      • [4.3 客户端:只调用模板方法](#4.3 客户端:只调用模板方法)
    • [5. 动态结构示例("流程骨架 + 运行时替换点")](#5. 动态结构示例(“流程骨架 + 运行时替换点”))
    • [6. 钩子方法(Hook)](#6. 钩子方法(Hook))
    • [7. 和代理模式对比](#7. 和代理模式对比)
      • [7.1 结构相似,意图不同](#7.1 结构相似,意图不同)
      • [7.2 关键差异](#7.2 关键差异)
    • [8. 优缺点](#8. 优缺点)
      • [8.1 优点](#8.1 优点)
      • [8.2 缺点](#8.2 缺点)
    • [9. 总结](#9. 总结)

前言

在面向对象设计里,有一种"访问前后要做事 "的需求。但代理模式(Proxy)更侧重于"替你去访问并控制访问 "。而模板方法模式(Template Method Pattern) 更关心的是:

一套固定流程(骨架算法)怎么写才能复用?哪些步骤允许子类替换?

它用来解决"流程很固定、但流程中的某些步骤可能变化"的问题。


1. 模板方法模式是什么?

模板方法模式:在抽象类中定义一个算法的骨架(模板方法),把通用步骤放在父类里,而把一些可变步骤延迟到子类中实现。

核心思想可以概括为:

  • 父类:定义"流程怎么走"(固定不变的骨架)
  • 子类:实现"流程里的可变步骤"(不同实现)
  • 客户端:通常只调用父类提供的模板方法,流程由它驱动

GoF 经典结构:

  • AbstractClass(抽象类/模板类):定义模板方法 + 通用逻辑
  • ConcreteClass(具体子类):实现抽象步骤/覆盖钩子步骤

2. 模板方法模式解决什么问题?

常见场景就是:流程固定,但步骤可变

例如:

  1. 数据处理流水线

    • 校验 -> 转换 -> 入库
      转换规则因类型不同而不同。
  2. 算法流程复用

    • 例如某类"关卡通关流程"
    • 判断条件/奖励发放在不同关卡不同。
  3. 游戏/业务的步骤化执行

    • 登录校验 -> 扣费 -> 下单
      扣费策略可能不同。
  4. 框架级流程(半成品流程)

    • 例如某些框架的"执行器":通用执行流程固定,可扩展点交给你填。

3. 实现步骤

写模板方法模式通常分为这几步:

  1. 抽象类里写模板方法(固定流程)

    使用 final(强烈建议)让流程骨架不被子类篡改。

  2. 把可变步骤声明为抽象方法或可覆盖方法

    • 抽象方法:子类必须实现
    • 钩子方法(Hook):提供默认实现或让子类可选择覆盖
  3. 子类只关心"自己要替换的步骤"

    • 不需要关心流程拼装顺序
  4. 客户端调用模板方法

    • 由父类驱动整个流程

4. 静态结构示例

下面用一个"做饭流程"的例子来体现:步骤顺序固定,但切菜/烹饪方式可变

4.1 抽象模板类:定义骨架流程

java 复制代码
public abstract class CookTemplate {

    // 模板方法:流程骨架(固定)
    public final void cook() {
        boilWater();     // 固定步骤1
        prepare();       // 可变步骤2(交给子类)
        cookFood();      // 可变步骤3(交给子类)
        plate();         // 固定步骤4
    }

    // 固定步骤:父类直接实现
    private void boilWater() {
        System.out.println("煮水中...");
    }

    // 可变步骤:交给子类实现(抽象方法)
    protected abstract void prepare();

    // 可变步骤:交给子类实现(抽象方法)
    protected abstract void cookFood();

    // 固定步骤
    private void plate() {
        System.out.println("装盘完成!");
    }
}

4.2 子类:实现可变步骤

java 复制代码
public class NoodleCook extends CookTemplate {
    @Override
    protected void prepare() {
        System.out.println("准备面条和调料");
    }

    @Override
    protected void cookFood() {
        System.out.println("煮面并调味");
    }
}

public class RiceCook extends CookTemplate {
    @Override
    protected void prepare() {
        System.out.println("准备米饭和配料");
    }

    @Override
    protected void cookFood() {
        System.out.println("蒸饭并加入配料");
    }
}

4.3 客户端:只调用模板方法

java 复制代码
public class Client {
    public static void main(String[] args) {
        CookTemplate cook1 = new NoodleCook();
        cook1.cook();

        CookTemplate cook2 = new RiceCook();
        cook2.cook();
    }
}

输出效果(顺序一致,差异在子类步骤):

  • 煮水中...
  • 准备面条和调料 / 准备米饭和配料
  • 煮面并调味 / 蒸饭并加入配料
  • 装盘完成!

5. 动态结构示例("流程骨架 + 运行时替换点")

你可以理解为模板方法模式的"动态性"来自两点:

  • cook() 是固定骨架(父类不变)
  • prepare() / cookFood() 在运行时由子类决定

也就是说:流程模板固定,但"插入点"是多态的


6. 钩子方法(Hook)

有时某个步骤不是"必须替换",而是"可选增强/可决定跳过"。

例如:决定是否执行某个动作:

java 复制代码
public abstract class PaymentTemplate {
    public final void pay() {
        verify();          // 固定
        if (shouldDiscount()) { // 钩子:默认 false,允许子类覆盖
            discount();
        }
        charge();          // 固定
        notifyUser();      // 固定
    }

    protected void verify() { System.out.println("校验订单"); }

    protected boolean shouldDiscount() { return false; } // 钩子(默认不打折)

    protected void discount() { System.out.println("执行打折"); }

    protected void charge() { System.out.println("扣款"); }

    protected void notifyUser() { System.out.println("通知用户"); }
}

public class VipPaymentTemplate extends PaymentTemplate {
    @Override
    protected boolean shouldDiscount() {
        return true; // VIP 开启打折
    }
}

7. 和代理模式对比

7.1 结构相似,意图不同

  • 代理模式 :你不直接调用真实对象,而是"通过一个代表去控制访问并增强"
  • 模板方法 :流程由模板类驱动,"步骤由子类替换",强调"算法骨架复用"

7.2 关键差异

  1. 谁控制顺序?

    • 代理模式:通常是 Proxy 在方法里"调用前后增强",但顺序不一定是"算法骨架固定"
    • 模板方法:父类明确规定"流程顺序 = 模板方法骨架",子类只填空
  2. 扩展点是什么?

    • 代理模式:扩展点是"访问前/访问后拦截逻辑"(权限、日志、缓存...)
    • 模板方法:扩展点是"流程中的某些步骤实现"(准备/执行/收尾等)
  3. 复用的是什么?

    • 代理模式:复用的是"控制访问 + 增强"的包装能力
    • 模板方法:复用的是"一整套固定流程(算法骨架)"

8. 优缺点

8.1 优点

  • 复用流程骨架:避免多个子类重复写相同的流程顺序
  • 提高扩展性:子类只改可变步骤,符合开闭原则
  • 降低出错概率 :模板方法可用 final 固定骨架,减少流程被改坏的风险

8.2 缺点

  • 类层级容易膨胀:可变步骤很多时,子类数量可能变多
  • 不适合流程差异很大的情况:如果每个实现连顺序都差很多,就不适合模板方法
  • 强耦合继承结构:模板方法主要依赖继承;如果你不想用继承,可以考虑策略模式等

9. 总结

模板方法模式的精髓是:

把一个算法/流程的"骨架"固定在父类里,把某些可变步骤交给子类实现。

这样既能复用整体流程,又能灵活替换其中的实现点。

相关推荐
牛奔2 小时前
g:Go 版本管理器安装与使用指南
开发语言·后端·golang
九皇叔叔2 小时前
005-SpringSecurity-Demo 配置外部文件映射
java·springboot·文件·springsecurity
Gent_倪2 小时前
Quartz 入门指南(二)Spring Boot + Quartz 示例
java·spring boot·quartz
唐不是营养物质2 小时前
无头浏览器chromedriver使用(目前不支持国产操作系统)
java·pdf
chenglin0162 小时前
Semantic Kernel 内核详解
后端·python·flask
zhishidi3 小时前
使用python给pdf文档自动添加目录书签
java·python·pdf
青柠代码录4 小时前
【SpringCloud】Nacos 组件:服务注册与发现
后端
WiChP11 小时前
【V0.1B5】从零开始的2D游戏引擎开发之路
java·服务器·数据库