Java案例中的模板方法模式怎么用?

wen java案例 1

深度解析Java模板方法模式:从经典案例到企业级应用实战

目录导读

  1. 模式本质:模板方法模式的核心定义与设计哲学
  2. 场景痛点:什么业务场景下必须使用该模式
  3. 经典案例:从框架源码到银行系统的完整代码实现
  4. 进阶技巧:钩子方法、好莱坞原则与性能优化
  5. 常见陷阱:继承滥用与模板方法反模式识别
  6. SEO问答:高频面试题与最佳实践总结

模式本质:让算法骨架与具体实现解耦

问:模板方法模式最核心的价值是什么?
答:定义一个操作中的算法骨架,而将一些步骤延迟到子类中实现,它使得子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。

Java案例中的模板方法模式怎么用?

真实案例对比
假如开发一个咖啡与茶冲泡系统

  • 共同步骤:烧水 → 冲泡 → 倒入杯子 → 加调料
  • 不同点:咖啡需要研磨豆子,茶需要浸泡茶叶;咖啡加糖和奶,茶加柠檬

用模板方法模式,父类定义冲泡流程(模板方法),子类只需实现“冲泡”和“加调料”两个抽象方法。

代码演示(简化版)

public abstract class Beverage {
    // 模板方法:不允许子类重写(final修饰)
    public final void prepareRecipe() {
        boilWater();
        brew();           // 抽象方法
        pourInCup();
        addCondiments();  // 抽象方法
    }
    private void boilWater() { System.out.println("烧水至100℃"); }
    private void pourInCup() { System.out.println("倒入杯子"); }
    protected abstract void brew();
    protected abstract void addCondiments();
}

设计哲学:遵循“开闭原则”——对扩展开放,对修改封闭,当新饮料出现时,只需新增子类,无需改动父类模板。


场景痛点:为什么你写的重复代码越来越多?

问:不用模板方法模式,直接用if-else行不行?
答:可以,但项目迭代3个月后会出现以下问题:

  1. 流程不一致:不同开发者在相同算法步骤中引入不同顺序
  2. 代码散落:重复的烧水、倒杯逻辑散落在N个类中
  3. 维护噩梦:修改烧水温度需要搜遍所有饮料类

企业级典型场景

  • 数据库连接池的初始化流程
  • 工作流引擎的审批节点执行
  • HTTP请求的拦截器链条(如Spring的HandlerInterceptor)
  • 支付系统中不同渠道(微信/支付宝/银联)的接口调用

经典案例:从Spring源码到银行系统的完整实现

案例1:Spring的AbstractApplicationContext

这是Java生态最著名的模板方法应用,其核心方法refresh()定义了IOC容器启动的13个步骤:

public abstract class AbstractApplicationContext {
    public void refresh() throws BeansException {
        // 1. 准备上下文
        prepareRefresh();
        // 2. 告诉子类刷新内部bean工厂
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // 3. 填充bean工厂特性
        prepareBeanFactory(beanFactory);
        // 4. 允许子类后处理bean工厂
        postProcessBeanFactory(beanFactory); // 钩子方法
        // ... 更多步骤
    }
    // 钩子方法:子类可覆盖,但默认空实现
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {}
}

案例2:银行转账系统(真实业务代码)

假设需要处理“转账”业务:

  • 所有渠道(柜面、网银、API)必须执行:校验账户 → 验证身份 → 执行转账 → 记录日志
  • 但柜面需插U盾,网银需输入短信验证码,API需校验签名

代码设计

public abstract class TransferProcessor {
    // 模板方法(final)
    public final boolean doTransfer(TransferRequest request) {
        if (!validateAccount(request)) return false;
        if (!verifyIdentity(request)) return false;
        if (!executeTransfer(request)) return false;
        logTransfer(request);
        return true;
    }
    // 公共逻辑(直接实现)
    private void logTransfer(TransferRequest req) {
        System.out.println("[日志] 转账成功: " + req.getAmount());
    }
    // 子类必须实现
    protected abstract boolean validateAccount(TransferRequest req);
    protected abstract boolean verifyIdentity(TransferRequest req);
    protected abstract boolean executeTransfer(TransferRequest req);
}
// 柜面转账子类
public class CounterTransfer extends TransferProcessor {
    @Override
    protected boolean validateAccount(TransferRequest req) {
        System.out.println("验证柜面账户余额");
        return true;
    }
    @Override
    protected boolean verifyIdentity(TransferRequest req) {
        System.out.println("验证U盾签名");
        return true;
    }
    @Override
    protected boolean executeTransfer(TransferRequest req) {
        System.out.println("柜面发起转账");
        return true;
    }
}

运行效果:调用方只需new CounterTransfer().doTransfer(request),无需关心具体流程。


进阶技巧:钩子方法与好莱坞原则

1 钩子方法(Hook Method)

问:模板方法能否支持“可选步骤”?
答:通过默认实现的钩子方法,子类根据需要决定是否覆盖。

经典场景:咖啡店提供“是否加糖”选项

public abstract class CaffeineBeverage {
    public final void prepareRecipe() {
        boilWater();
        brew();
        if (customerWantsCondiments()) {  // 钩子方法
            addCondiments();
        }
        pourInCup();
    }
    // 钩子方法:默认返回true
    protected boolean customerWantsCondiments() {
        return true;
    }
}

子类可通过覆盖customerWantsCondiments()返回false来跳过加调料步骤。

2 好莱坞原则(Hollywood Principle)

“不要调用我们,我们会调用你。”——控制反转的一种体现。
模板方法模式中,父类控制算法流程,子类只提供具体实现,正是好莱坞原则的典型应用。


常见陷阱:过度使用模板方法会带来什么?

陷阱1:继承层级过深

症状:抽象类A继承抽象类B,B继承抽象类C...调用链超过5层
后果:修改任何一层模板方法,都可能影响全部分支

解决:优先考虑组合+策略模式,代替继承+模板方法

陷阱2:模板方法过于庞大

反例:一个模板方法包含30个步骤,子类被迫实现所有抽象方法
后果:子类代码膨胀,且很多步骤其实有默认实现

解决:使用钩子方法减少抽象方法数量;或拆分模板方法为多个小模板

陷阱3:违反里氏替换原则(LSP)

症状:子类修改了父类模板方法的行为(如覆盖了非abstract方法)
后果:无法保证算法骨架稳定性

铁律:父类模板方法必须用final修饰,公共步骤用private,抽象步骤用protected abstract


SEO问答:高频面试题与最佳实践总结

Q1:模板方法模式与策略模式的区别?

  • 模板方法:定义算法骨架,子类实现具体步骤(继承)
  • 策略模式:定义算法接口,客户端选择算法实现(组合)
  • 选择依据:如果算法步骤固定但实现不同 → 用模板方法;如果算法整体可替换 → 用策略模式

Q2:在JDK源码中哪里使用了模板方法?

  • java.io.InputStreamread(byte[])调用抽象方法read()
  • java.util.AbstractListaddAll()调用add()
  • javax.servlet.http.HttpServletdoGet()doPost()service()模板方法调用

Q3:模板方法模式如何提升代码可测试性?

  • 父类模板方法通过子类注入测试桩(Mock)
  • 每个抽象方法可独立测试
  • TransferProcessor可以通过构造Mock子类来测试日志记录逻辑

最佳实践清单

  1. 模板方法标记为final,子类不可重写
  2. 所有重复的算法步骤放在父类私有方法中
  3. 钩子方法使用protected提供默认实现
  4. 子类只覆盖抽象方法,不重写任何非抽象方法
  5. 当子类数量超过10个时,考虑引入枚举类型+工厂模式替代继承

模板方法模式是Java设计模式中“控制反转”思想最直观的体现,它通过将算法骨架固定在父类中,既保证了业务逻辑的一致性,又给扩展留下了优雅接口,在实际项目中,建议配合单元测试和代码审查,防止继承滥用导致的类爆炸问题。

推荐阅读

  • 《Head First 设计模式》第8章
  • Spring Framework源码中的AbstractApplicationContext
  • 阿里巴巴Java开发手册中关于设计模式的规范章节

抱歉,评论功能暂时关闭!