13、(行为型设计模式)Java 模板方法模式深度学习指南

以下是为你精心撰写的 《Java 模板方法模式深度学习指南》,专为 Java 后端开发者打造,内容涵盖:定义、作用、与策略模式的深度对比、真实业务场景、实现方式、Spring 集成、避坑指南、最佳实践、面试高频题,所有示例均含中文注释,可直接用于项目重构、订单流程、审批流、数据导入导出、报表生成等核心场景。


📘 Java 模板方法模式深度学习指南

—— 从“重复流程”到“固定骨架,灵活实现”的架构升华

作者:Java 后端架构实战导师
适用对象:Java 后端开发者(Spring Boot / 微服务 / 订单系统 / 审批流 / 数据处理)
目标:彻底掌握“定义算法骨架,让子类重写具体步骤”的经典设计模式,告别“复制粘贴”和“流程混乱”
核心原则“我规定你必须按这个流程走,但每一步你怎么干,你说了算。”


✅ 一、什么是模板方法模式?

📌 定义:

模板方法模式(Template Method Pattern) 是一种行为型设计模式,它定义了一个算法的骨架(即固定的执行流程),并将一些具体步骤延迟到子类中实现。子类可以重写这些步骤,但不改变算法的整体结构。

🔍 核心思想:

  • 把一个固定流程(如:下单 → 扣库存 → 支付 → 发货 → 通知)封装在父类
  • 将流程中可变的部分(如:支付方式、通知方式)定义为抽象方法或钩子方法
  • 子类继承父类重写具体步骤,实现自己的业务逻辑
  • 客户端调用父类的模板方法,自动执行完整流程

💡 一句话记忆
“我给你一个标准流程,你按这个顺序做,但每一步怎么做,你自己决定。”

🆚 模板方法模式 vs 策略模式(极易混淆)

维度模板方法模式策略模式
目的固定流程,灵活步骤固定接口,灵活算法
控制权父类控制流程,子类控制步骤客户端控制选择哪个算法
实现方式继承(Is-A)组合(Has-A)
调用方式客户端调用父类方法客户端调用策略对象
是否可变流程❌ 流程固定✅ 算法可变,流程可换
典型场景订单流程、审批流、数据导入支付方式、排序算法、折扣计算
类比做饭流程:洗菜 → 切菜 → 炒菜 → 装盘(你决定炒什么菜)你选择用锅炒、用电饭锅煮、用微波炉热

关键区别

  • 模板方法“你必须按我的流程走,但每一步你自由发挥”
  • 策略模式“你可以选不同的流程,但每个流程的接口是一样的”

经典比喻
你去餐厅吃饭:

  • 模板方法:餐厅规定“先点菜 → 等待上菜 → 吃饭 → 结账”,但“吃什么菜”你决定
  • 策略模式:你可以选“中餐”、“西餐”、“日料”三种不同流程

✅ 二、模板方法模式有什么作用?(Why Use Template Method?)

作用说明
避免代码重复公共流程只写一次,子类复用
强制流程规范确保所有子类都按统一顺序执行关键步骤
提高可维护性修改流程只需改父类,影响所有子类
支持开闭原则新增流程变体只需新增子类,不改父类
增强可读性流程结构清晰,一目了然
支持钩子方法子类可选择性覆盖某些步骤,实现灵活扩展
便于测试可对父类流程做集成测试,对子类步骤做单元测试
Spring 集成基础Spring 的 AbstractApplicationContextJdbcTemplate 都是模板方法模式

💡 经典名言
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.
——《Design Patterns: Elements of Reusable Object-Oriented Software》


✅ 三、模板方法模式的典型使用场景(Java 后端真实案例)

场景说明是否推荐使用模板方法模式
订单创建流程校验 → 扣库存 → 支付 → 生成物流 → 发送通知✅ 强烈推荐
用户注册流程验证邮箱 → 发送验证码 → 创建账户 → 绑定手机号 → 发送欢迎邮件✅ 强烈推荐
审批流程提交 → 部门审核 → 财务审核 → 总监审批 → 归档✅ 强烈推荐
数据导入读取文件 → 校验格式 → 解析数据 → 验证业务规则 → 批量入库 → 生成报告✅ 推荐
数据导出查询数据 → 格式化 → 生成文件 → 发送邮件/下载✅ 推荐
报表生成连接数据库 → 查询数据 → 计算指标 → 生成图表 → 输出PDF/Excel✅ 推荐
任务调度准备资源 → 执行任务 → 检查结果 → 记录日志 → 发送通知✅ 推荐
缓存预热加载配置 → 初始化连接 → 预加载数据 → 启动定时任务✅ 推荐
微服务启动加载配置 → 初始化组件 → 注册服务 → 开启端口 → 启动健康检查✅ 推荐
支付方式选择支付宝、微信、银联,只是算法不同❌ 用策略模式
排序算法快排、冒泡、归并❌ 用策略模式
单一操作只有“保存用户”一个动作❌ 直接写方法

判断标准
“有一个固定流程,包含多个步骤,其中部分步骤的实现因业务而异?”
→ 是 → 用模板方法模式!


✅ 四、模板方法模式的三种实现方式详解(含中文注释)

我们从基础结构Spring 模板类,逐步深入。


🔹 1. 基础结构:抽象类 + 抽象方法 + 钩子方法

/**
 * 【1】抽象模板类:定义算法骨架
 */
public abstract class OrderProcessTemplate {

    /**
     * 【核心模板方法】:定义固定流程,final 防止被重写
     */
    public final void processOrder(String orderId) {
        System.out.println("📦 开始处理订单:" + orderId);
        validateOrder(orderId);       // 步骤1:校验订单
        calculateTotal(orderId);      // 步骤2:计算金额
        applyDiscount(orderId);       // 步骤3:应用折扣(子类实现)
        saveOrder(orderId);           // 步骤4:保存订单
        notifyUser(orderId);          // 步骤5:通知用户
        System.out.println("✅ 订单处理完成\n");
    }

    /**
     * 【公共方法】:所有子类共享的逻辑
     */
    protected void validateOrder(String orderId) {
        System.out.println("🔧 步骤1:校验订单是否存在");
    }

    protected void calculateTotal(String orderId) {
        System.out.println("💰 步骤2:计算订单总金额");
    }

    /**
     * 【抽象方法】:子类必须实现(强制步骤)
     */
    protected abstract void applyDiscount(String orderId);

    /**
     * 【公共方法】:通用逻辑
     */
    protected void saveOrder(String orderId) {
        System.out.println("💾 步骤4:保存订单到数据库");
    }

    /**
     * 【钩子方法】:子类可选择性重写(非强制)
     */
    protected void notifyUser(String orderId) {
        System.out.println("📢 步骤5:发送短信通知用户");
    }

    // 可添加更多钩子方法
    protected boolean isExpressDelivery(String orderId) {
        return false; // 默认不加急
    }
}

/**
 * 【2】具体子类1:普通订单
 */
class RegularOrderProcess extends OrderProcessTemplate {
    @Override
    protected void applyDiscount(String orderId) {
        System.out.println("📉 步骤3:普通订单无折扣");
        // 不打折
    }

    // 可选择性重写钩子方法
    @Override
    protected boolean isExpressDelivery(String orderId) {
        return true; // 普通订单也加急
    }

    @Override
    protected void notifyUser(String orderId) {
        System.out.println("📱 步骤5:发送短信 + 邮件通知用户");
    }
}

/**
 * 【3】具体子类2:VIP订单
 */
class VIPOrderProcess extends OrderProcessTemplate {
    @Override
    protected void applyDiscount(String orderId) {
        System.out.println("🎁 步骤3:VIP订单享受9折优惠");
        // 9折
    }

    // 不重写钩子方法,使用默认实现
}

/**
 * 【4】客户端:调用模板方法
 */
public class TemplateMethodDemo {
    public static void main(String[] args) {
        // 创建普通订单流程
        OrderProcessTemplate regular = new RegularOrderProcess();
        regular.processOrder("ORD001");

        // 创建VIP订单流程
        OrderProcessTemplate vip = new VIPOrderProcess();
        vip.processOrder("ORD002");
    }
}
✅ 输出结果:
📦 开始处理订单:ORD001
🔧 步骤1:校验订单是否存在
💰 步骤2:计算订单总金额
📉 步骤3:普通订单无折扣
💾 步骤4:保存订单到数据库
📱 步骤5:发送短信 + 邮件通知用户
✅ 订单处理完成

📦 开始处理订单:ORD002
🔧 步骤1:校验订单是否存在
💰 步骤2:计算订单总金额
🎁 步骤3:VIP订单享受9折优惠
💾 步骤4:保存订单到数据库
📢 步骤5:发送短信通知用户
✅ 订单处理完成
✅ 优点:
  • 流程固定:保证所有订单都走“校验 → 计算 → 折扣 → 保存 → 通知”流程
  • 可扩展:新增“企业订单”只需新建子类
  • 复用性强:公共逻辑(validate、save)只写一次
  • 钩子灵活isExpressDelivery() 可选重写,不强制
⚠️ 注意:
  • 模板方法必须是 final,防止子类修改流程顺序
  • 抽象方法必须由子类实现
  • 钩子方法有默认实现,子类可选覆盖

🔹 2. 模板方法模式 + Spring 模板类(企业级实战)

Spring 的 JdbcTemplateRestTemplateTransactionTemplate 都是模板方法模式的典范!

/**
 * 【1】抽象模板类:模拟 JdbcTemplate
 */
public abstract class DatabaseTemplate {

    /**
     * 模板方法:执行数据库操作
     */
    public final <T> T execute(String sql, RowMapper<T> rowMapper) {
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;

        try {
            // 1. 获取连接
            conn = getConnection();
            System.out.println("🔌 获取数据库连接");

            // 2. 准备语句
            stmt = conn.prepareStatement(sql);
            System.out.println("📝 准备SQL语句:" + sql);

            // 3. 执行查询
            rs = stmt.executeQuery();
            System.out.println("🔍 执行查询");

            // 4. 映射结果(交给子类实现)
            T result = mapRow(rs, rowMapper);
            System.out.println("📊 映射结果");

            return result;

        } catch (Exception e) {
            System.err.println("❌ 数据库操作失败:" + e.getMessage());
            throw new RuntimeException("数据库异常", e);
        } finally {
            // 5. 关闭资源(固定清理)
            closeResources(conn, stmt, rs);
            System.out.println("🗑️ 关闭数据库连接");
        }
    }

    /**
     * 【抽象方法】:子类负责映射结果
     */
    protected abstract <T> T mapRow(ResultSet rs, RowMapper<T> rowMapper);

    /**
     * 【公共方法】:获取连接
     */
    protected Connection getConnection() {
        // 实际项目中:从连接池获取
        return new Connection() { // 模拟
            @Override
            public PreparedStatement prepareStatement(String sql) {
                return new PreparedStatement() {
                    @Override
                    public ResultSet executeQuery() {
                        // 模拟返回结果集
                        return new ResultSet() {
                            @Override
                            public boolean next() {
                                return true; // 有数据
                            }

                            @Override
                            public String getString(String columnLabel) {
                                return "张三";
                            }
                        };
                    }
                };
            }
        };
    }

    /**
     * 【公共方法】:关闭资源
     */
    protected void closeResources(Connection conn, PreparedStatement stmt, ResultSet rs) {
        // 真实项目中:try-with-resources 或工具类关闭
    }
}

/**
 * 【2】结果映射器接口
 */
interface RowMapper<T> {
    T mapRow(ResultSet rs) throws SQLException;
}

/**
 * 【3】具体实现:用户查询模板
 */
class UserQueryTemplate extends DatabaseTemplate {

    @Override
    protected <T> T mapRow(ResultSet rs, RowMapper<T> rowMapper) {
        // 调用传入的 RowMapper
        return (T) rowMapper.mapRow(rs);
    }

    /**
     * 提供便捷方法:查询单个用户
     */
    public User findUserById(Long id) {
        String sql = "SELECT * FROM users WHERE id = ?";
        return execute(sql, rs -> {
            if (rs.next()) {
                return new User(rs.getLong("id"), rs.getString("name"), rs.getString("email"));
            }
            return null;
        });
    }
}

/**
 * 【4】数据对象
 */
record User(Long id, String name, String email) {}

/**
 * 【5】客户端使用
 */
public class SpringTemplateDemo {
    public static void main(String[] args) {
        UserQueryTemplate template = new UserQueryTemplate();

        // ✅ 客户端只关心“我要查什么”,不关心连接、关闭、异常
        User user = template.findUserById(1L);
        System.out.println("👤 查询结果:" + user);
        // 输出:
        // 🔌 获取数据库连接
        // 📝 准备SQL语句:SELECT * FROM users WHERE id = ?
        // 🔍 执行查询
        // 📊 映射结果
        // 🗑️ 关闭数据库连接
        // 👤 查询结果:User[id=1, name=张三, email=zhangsan@xx.com]
    }
}
✅ 优点:
  • 完全解耦:客户端只写 SQL 和映射逻辑
  • 资源安全:连接自动关闭,防止泄漏
  • 异常统一:所有异常包装为 RuntimeException
  • Spring 模板类JdbcTemplateRestTemplateTransactionTemplate 都是这样设计的!

🔹 3. 模板方法模式 + Spring Boot 自动配置(高级实战)

在 Spring Boot 中,自动配置类 就是模板方法模式的体现!

/**
 * 【1】抽象模板:定义数据导入流程
 */
public abstract class DataImportTemplate {

    /**
     * 模板方法:固定导入流程
     */
    public final void importData(String filePath) {
        System.out.println("📥 开始导入数据文件:" + filePath);

        // 步骤1:读取文件
        List<String> lines = readFile(filePath);
        System.out.println("📄 读取文件行数:" + lines.size());

        // 步骤2:解析每一行(子类实现)
        List<DataRecord> records = parseLines(lines);
        System.out.println("🧩 解析数据记录数:" + records.size());

        // 步骤3:校验数据(子类实现)
        validateRecords(records);
        System.out.println("✅ 数据校验通过");

        // 步骤4:批量保存(子类实现)
        saveRecords(records);
        System.out.println("💾 批量保存成功");

        // 步骤5:发送通知(钩子方法)
        sendNotification(records.size());
        System.out.println("📢 通知发送完成");

        System.out.println("🎉 数据导入完成\n");
    }

    // 公共方法:读取文件(固定)
    protected List<String> readFile(String filePath) {
        // 模拟读取
        return List.of("张三,13800138000,zhangsan@xx.com", "李四,13900139000,lisi@xx.com");
    }

    // 抽象方法:子类实现解析逻辑
    protected abstract List<DataRecord> parseLines(List<String> lines);

    // 抽象方法:子类实现校验逻辑
    protected abstract void validateRecords(List<DataRecord> records);

    // 抽象方法:子类实现保存逻辑
    protected abstract void saveRecords(List<DataRecord> records);

    // 钩子方法:可选重写
    protected void sendNotification(int count) {
        System.out.println("📧 发送邮件通知:成功导入 " + count + " 条记录");
    }
}

/**
 * 【2】数据记录对象
 */
record DataRecord(String name, String phone, String email) {}

/**
 * 【3】具体实现1:导入用户数据
 */
@Component
public class UserImportTemplate extends DataImportTemplate {

    @Autowired
    private UserService userService;

    @Override
    protected List<DataRecord> parseLines(List<String> lines) {
        return lines.stream()
                .map(line -> {
                    String[] parts = line.split(",");
                    return new DataRecord(parts[0], parts[1], parts[2]);
                })
                .toList();
    }

    @Override
    protected void validateRecords(List<DataRecord> records) {
        for (DataRecord record : records) {
            if (record.name().isEmpty() || record.phone().isEmpty()) {
                throw new IllegalArgumentException("用户信息不完整:" + record);
            }
        }
    }

    @Override
    protected void saveRecords(List<DataRecord> records) {
        records.forEach(record -> {
            userService.createUser(record.name(), record.phone(), record.email());
        });
    }

    // 重写钩子方法:发送短信通知
    @Override
    protected void sendNotification(int count) {
        System.out.println("📱 发送短信通知:成功导入 " + count + " 个用户");
    }
}

/**
 * 【4】用户服务
 */
@Service
class UserService {
    public void createUser(String name, String phone, String email) {
        System.out.println("👤 创建用户:" + name + ", 手机:" + phone + ", 邮箱:" + email);
    }
}

/**
 * 【5】启动类
 */
@SpringBootApplication
public class TemplateBootDemo implements CommandLineRunner {

    @Autowired
    private UserImportTemplate userImportTemplate;

    public static void main(String[] args) {
        SpringApplication.run(TemplateBootDemo.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        userImportTemplate.importData("users.csv");
    }
}
✅ 输出:
📥 开始导入数据文件:users.csv
📄 读取文件行数:2
🧩 解析数据记录数:2
✅ 数据校验通过
👤 创建用户:张三, 手机:13800138000, 邮箱:zhangsan@xx.com
👤 创建用户:李四, 手机:13900139000, 邮箱:lisi@xx.com
💾 批量保存成功
📱 发送短信通知:成功导入 2 个用户
📢 通知发送完成
🎉 数据导入完成
✅ 优点:
  • 可插拔:新增“商品导入”只需新建 ProductImportTemplate
  • Spring 管理:自动注入 UserService
  • 配置驱动:可通过 @Profile 控制不同环境使用不同模板

✅ 五、模板方法模式 vs 策略模式 对比总结表

维度模板方法模式策略模式
目的固定流程,灵活步骤固定接口,灵活算法
实现方式继承(Is-A)组合(Has-A)
控制权父类控制流程,子类控制步骤客户端控制选择哪个策略
调用方式客户端调用父类模板方法客户端调用策略对象的方法
灵活性流程固定,步骤可变流程可换,算法固定
典型场景订单流程、审批流、数据导入支付方式、排序算法、折扣策略
Spring 实现JdbcTemplateTransactionTemplateComparatorMessageConverter

一句话区分

  • 模板方法“你必须按这个流程走,但每一步你自由发挥”
  • 策略模式“你可以选不同的流程,但每个流程的接口是一样的”

✅ 六、模板方法模式的避坑指南(Java 后端高频踩坑)

问题原因解决方案
❌ 模板方法不是 final子类重写流程顺序✅ 所有模板方法必须声明为 final
❌ 抽象方法写成具体方法子类无法重写✅ 需要子类实现的必须是 abstract
❌ 钩子方法没有默认实现子类必须实现✅ 钩子方法应有默认空实现
❌ 在模板方法中调用子类的非抽象方法可能调用未初始化对象✅ 只调用 finalabstractprotected 方法
❌ 模板方法中包含业务逻辑违反单一职责✅ 模板方法只控制流程,业务逻辑放子类
❌ 使用模板方法做“单一操作”过度设计✅ 至少 3 个步骤,且有可变部分才用

✅ 七、学习建议与进阶路径

阶段建议
📚 第一周将你项目中的“用户注册流程”重构为模板方法(校验 → 发验证码 → 创建账户 → 发欢迎邮件)
📚 第二周用模板方法实现“数据导入”流程(读取 → 解析 → 校验 → 保存 → 通知)
📚 第三周阅读 Spring 源码:JdbcTemplate.execute()TransactionTemplate.execute() 如何实现?
📚 第四周在 Spring Boot 中,创建一个 DataImportTemplate,支持不同数据格式(CSV、Excel)
📚 面试准备准备回答:

“你项目中哪里用了模板方法?”
“模板方法和策略模式区别?”
“Spring 中的 JdbcTemplate 是模板方法吗?”
“钩子方法有什么用?” |


✅ 八、模板方法模式面试高频题(附答案)

Q1:模板方法模式解决了什么问题?

A:解决了“多个子类有相同流程,但部分步骤不同”的问题,避免代码重复,强制流程规范。

Q2:模板方法模式和策略模式的区别?

A:

  • 模板方法:继承 + 固定流程,子类实现步骤
  • 策略模式:组合 + 固定接口,客户端选择算法
    → 一个是“你必须按我的流程做”,一个是“你可以选不同的流程”

Q3:Spring 中的 JdbcTemplate 是模板方法模式吗?

A:是的!JdbcTemplate.execute() 是模板方法,RowMapper.mapRow() 是抽象方法,客户端只需实现映射逻辑。

Q4:什么是钩子方法?

A:钩子方法(Hook Method)是模板方法中有默认实现的 protected 方法,子类可选择性重写,用于扩展功能而不改变流程。

Q5:为什么模板方法要声明为 final?

A:防止子类重写整个流程,破坏算法结构,确保流程的完整性。


✅ 九、总结:模板方法模式选型决策树

graph TD
    A[是否有多个子类共享相同流程?] --> B{流程固定,但部分步骤可变?}
    B -->|是| C[使用模板方法模式]
    B -->|否| D[使用策略模式或直接写方法]

最终推荐

  • 流程固定、步骤可变 → ✅ 模板方法模式(最标准)
  • 算法可变、流程可换 → ✅ 策略模式
  • 流程简单、无变化 → ❌ 不用模式,直接写方法

✅ 十、结语:真正的高手,不教别人怎么做,只告诉他们必须按这个顺序做

你不是在学“模板方法模式”,你是在学“如何为复杂流程建立标准化、可复用、可扩展的框架”。

当你看到 JdbcTemplate 只让你写 SQL 和映射,连接、关闭、异常、事务全由它管理时,你已经掌握了 Spring 的灵魂。
当你用一个 OrderProcessTemplate 统一管理所有订单流程,新增“企业订单”只需一个类时,你已经是架构师了。

不要为了“用模式”而用模式,
要为了“系统可维护、可扩展、可规范”而选择它。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值