模板方法模式:把算法骨架焊死,把细节留给孩子

说实话,模板方法模式是我在重构老系统时用得最多的模式之一。它不花哨,但特别实在。你想想看,当你在一个团队里维护一套代码,发现好几个类都在做类似的事情——步骤差不多,就是中间某几步不一样。这时候,模板方法就该登场了。

它的核心思想很简单:在父类里定义好算法的骨架,把某些步骤的实现延迟到子类中。说白了,就是「流程我定好了,具体怎么做你们看着办」。

一句话总结:模板方法模式让你在不改变算法结构的前提下,允许子类重新定义算法中的某些特定步骤。

为什么需要它?

我在几年前接手过一个支付系统。当时系统支持微信支付、支付宝、银联三种渠道。每个渠道的支付流程其实差不多:

  1. 校验参数
  2. 构建请求数据
  3. 调用第三方接口
  4. 处理响应
  5. 更新订单状态

但每个渠道的签名算法、请求格式、回调处理都不一样。一开始,团队里每个人各写各的,代码里充斥着大量重复的「校验-构建-调用-处理」逻辑。后来我实在看不下去了,用模板方法模式重构了一遍。嗯,效果立竿见影。

我的习惯:只要发现两个以上的类在做「步骤相同、细节不同」的事情,我就会考虑模板方法模式。它比复制粘贴强一百倍。

模式结构

模板方法模式涉及两个角色:

  • 抽象类(AbstractClass):定义算法的骨架,包含一个模板方法和若干个基本方法。
  • 具体类(ConcreteClass):实现抽象类中定义的基本方法,完成算法中可变的部分。

这里有个关键点:模板方法一般用 final 修饰,防止子类重写整个流程。基本方法则分为三类:

类型 说明 示例
抽象方法 必须由子类实现 signData()
具体方法 父类已实现,子类可选覆盖 validateParams()
钩子方法 默认空实现,子类按需覆盖 beforeRequest()

代码示例:支付流程

咱们直接看代码。这是我当年重构支付系统时的简化版本:

// 抽象类:定义支付流程骨架
public abstract class PaymentProcessor {

    // 模板方法,用 final 防止子类篡改流程
    public final void processPayment(Order order) {
        validateOrder(order);
        String requestData = buildRequest(order);
        String response = callThirdParty(requestData);
        handleResponse(response);
        updateOrderStatus(order);
    }

    // 具体方法:参数校验,所有渠道都一样
    private void validateOrder(Order order) {
        if (order == null || order.getAmount() <= 0) {
            throw new IllegalArgumentException("订单无效");
        }
        System.out.println("订单校验通过");
    }

    // 抽象方法:构建请求数据,各渠道不同
    protected abstract String buildRequest(Order order);

    // 抽象方法:调用第三方接口,各渠道不同
    protected abstract String callThirdParty(String requestData);

    // 钩子方法:处理响应前的预处理,子类可选实现
    protected void beforeHandleResponse(String response) {
        // 默认什么都不做
    }

    // 具体方法:处理响应,大部分渠道逻辑相同
    private void handleResponse(String response) {
        beforeHandleResponse(response); // 调用钩子
        if (response.contains("success")) {
            System.out.println("支付成功");
        } else {
            System.out.println("支付失败");
        }
    }

    // 具体方法:更新订单状态
    private void updateOrderStatus(Order order) {
        order.setStatus("PAID");
        System.out.println("订单状态已更新");
    }
}

// 具体类:微信支付
public class WechatPayment extends PaymentProcessor {
    @Override
    protected String buildRequest(Order order) {
        // 微信特有的 XML 格式
        return "<xml><appid>wx123</appid><total_fee>" 
               + order.getAmount() + "</total_fee></xml>";
    }

    @Override
    protected String callThirdParty(String requestData) {
        System.out.println("调用微信支付接口");
        return "success";
    }

    @Override
    protected void beforeHandleResponse(String response) {
        System.out.println("微信支付:验证签名");
    }
}

// 具体类:支付宝支付
public class AlipayPayment extends PaymentProcessor {
    @Override
    protected String buildRequest(Order order) {
        // 支付宝特有的 JSON 格式
        return "{\"app_id\":\"alipay456\",\"total_amount\":\"" 
               + order.getAmount() + "\"}";
    }

    @Override
    protected String callThirdParty(String requestData) {
        System.out.println("调用支付宝接口");
        return "success";
    }
}

注意:我曾经见过有人把模板方法写成非 final 的,结果子类直接重写了整个流程,把骨架改得面目全非。这完全违背了模板方法模式的初衷。记住:模板方法必须是 final 的,除非你有非常特殊的理由。

钩子方法:给子类留个「后门」

你可能会问:如果子类想在某个步骤前后加点自己的逻辑,但又不想破坏骨架,怎么办?

答案就是钩子方法。它是个空方法,子类可以按需覆盖。比如上面的 beforeHandleResponse(),微信支付用它来验证签名,支付宝不需要就直接跳过。这种设计非常灵活,我特别喜欢。

我个人习惯在模板方法里放两三个钩子:一个在开始前,一个在关键步骤后,一个在结束前。这样既保证了流程的稳定性,又给了子类足够的扩展空间。

SVG 流程图:模板方法模式的核心逻辑

模板方法模式执行流程 1. 校验参数(具体方法) 2. 构建请求(抽象方法) 钩子:beforeRequest 3. 调用第三方接口(抽象方法) 4. 处理响应(具体方法) 钩子:beforeHandleResponse 5. 更新订单状态(具体方法) 模板方法(final)控制整体流程,子类只需实现抽象方法和钩子方法

什么时候用?什么时候别用?

我总结了几条经验,供你参考:

  • 适合用:多个类有相同的算法步骤,只是某些步骤的实现不同。比如数据导入导出、批处理任务、测试用例执行流程。
  • 不适合用:算法步骤经常变化,或者子类需要大幅修改流程。这时候用模板方法反而会绑死你。
  • 避坑:不要为了用模式而用模式。我曾经见过有人把只有两个步骤的简单逻辑也套上模板方法,结果代码反而更复杂了。

我的建议:模板方法模式最适合「80%相同,20%不同」的场景。如果相同部分少于50%,不如考虑策略模式或者组合模式。

与策略模式的区别

很多人会把模板方法和策略模式搞混。我简单说一下:

  • 模板方法:在父类控制流程,子类实现细节。是「继承」的体现。
  • 策略模式:把算法封装成独立的策略类,通过组合来替换整个算法。是「组合」的体现。

说白了,模板方法是在「流程不变、细节可变」的场景下用的。策略模式是在「整个算法都可以替换」的场景下用的。两者各有千秋,选对场景才是关键。

最后说两句

模板方法模式是我个人非常喜欢的一个模式。它不复杂,但很实用。尤其是在做框架设计或者重构遗留系统时,它能帮你把重复的流程收拢到一处,让代码变得清晰可控。

嗯,如果你在项目中遇到「流程固定、实现多变」的场景,不妨试试模板方法模式。它不会让你失望的。


公众号:蓝海资料掘金营,微信deep3321