14、Java 模板方法模式从入门到实战

Java 模板方法模式从入门到实战(后端必看,附案例+面试考点)

前言:模板方法模式(Template Method Pattern)是Java设计模式中最易理解、最常用的"行为型模式"之一,核心是"定义固定流程模板,将可变步骤延迟到子类实现",无需修改模板即可灵活定制流程细节。

很多Java后端开发者在开发中会遇到这样的问题:面对多个相似的业务流程(如不同类型的订单处理、多种数据导出、各类接口调用流程),每个流程的核心步骤一致,但部分细节不同,习惯重复编写流程代码,导致代码冗余、维护成本高;面试时被问到"模板方法模式的核心是什么""模板方法与策略模式的区别""框架中哪里用到了模板方法",常常答不到重点。

本文从入门到实战,用极简语言拆解模板方法模式核心,结合可直接复制运行的入门案例、真实业务实战(Spring Boot环境),以及高频面试考点,带你吃透模板方法模式------新手能快速上手,中级开发者能落地项目,面试时能轻松应对追问,看完就能用在实际开发中。

一、为什么Java后端必须掌握模板方法模式?(痛点直击)

先看3个Java后端开发中最常见的场景,你一定遇到过,这也是模板方法模式的核心应用场景,更是面试中高频提及的"流程复用"典型场景:

  • 场景1:电商订单处理流程,无论是普通订单、秒杀订单、预售订单,核心流程都是"校验订单→扣减库存→生成订单→通知用户",只有"校验订单"和"通知用户"的细节不同,若重复编写完整流程,代码冗余严重;

  • 场景2:后台管理系统的数据导出功能,支持Excel、CSV、PDF三种格式,核心流程都是"查询数据→格式化数据→导出文件→记录日志",仅"格式化数据"和"导出文件"的逻辑不同,重复开发会增加维护成本;

  • 场景3:第三方接口调用流程,调用微信、支付宝、银联的接口,核心流程都是"参数封装→签名验证→发送请求→结果解析",只有"签名验证"和"结果解析"的细节不同,重复编写流程会导致代码臃肿。

这些场景的共性问题:多个相似流程,核心步骤固定,仅部分细节不同,重复编写流程代码,导致冗余、可维护性差、扩展性弱

而模板方法模式的核心价值,就是"流程复用+灵活定制"------将固定的核心流程定义为"模板",把可变的细节步骤抽象出来,让子类按需实现,无需修改模板即可扩展新的流程细节,完美符合"开闭原则"。

简单说,模板方法模式就是"定好流程框架,填不同的细节"。比如订单处理,先定义好4个核心步骤的模板,普通订单、秒杀订单只需各自实现"校验"和"通知"的细节,无需重复编写"扣减库存""生成订单"的代码,极大提升开发效率和代码可维护性。

核心结论:模板方法模式不是"花里胡哨"的设计,而是后端开发的"流程复用神器"------初级开发者用它减少代码冗余,中级开发者用它设计可扩展的业务流程,高级开发者用它理解框架底层设计(如Spring的生命周期、MyBatis的执行流程),面试时更是高频考点(中高级岗位必问)。

二、模板方法模式核心概念(极简入门,无需死记硬背)

模板方法模式的本质很简单:在抽象父类中定义一个"流程模板"(模板方法),将流程中的固定步骤封装为具体方法,可变步骤封装为抽象方法,子类继承父类后,实现抽象方法即可定制流程细节,无需修改模板方法本身

就像做奶茶:核心流程(模板)是"煮茶→加配料→摇匀→装杯",其中"煮茶""摇匀""装杯"是固定步骤,"加配料"是可变步骤(加珍珠、加芋圆、加椰果);抽象父类定义这4个步骤的模板,子类(珍珠奶茶类、芋圆奶茶类)只需实现"加配料"的细节,即可完成不同奶茶的制作,无需重复编写其他固定步骤。

2.1 核心角色(2个核心,必记,区分角色是掌握模板方法模式的关键)

  1. 抽象模板角色(Abstract Template):定义流程模板,包含一个"模板方法"(定义固定流程的执行顺序)和两类方法------固定方法(核心步骤,父类实现,子类无需修改)、抽象方法(可变步骤,父类声明,子类必须实现);

  2. 具体模板角色(Concrete Template):继承抽象模板类,实现抽象方法,定制流程中的可变细节,无需修改模板方法和固定步骤。

核心原则:抽象模板定流程,具体模板填细节;固定步骤父类写,可变步骤子类改。这是模板方法模式的灵魂,也是它与其他行为型模式(如策略模式)的关键区别。

核心注意点:模板方法模式的核心是"固定流程+可变细节",解决的是"相似流程复用"的问题,与策略模式的"算法动态切换"、装饰器模式的"功能增强"有本质区别。

2.2 模板方法模式的核心流程(一句话看懂)

抽象父类定义模板方法(固定步骤顺序)→ 父类实现固定步骤,声明抽象的可变步骤 → 子类继承父类,实现抽象步骤 → 客户端调用子类对象的模板方法,自动执行完整流程(固定步骤+子类定制的可变步骤)。

三、模板方法模式入门实现(附可复制代码,新手必练)

以"奶茶制作流程"为入门案例,模拟3种奶茶(珍珠奶茶、芋圆奶茶、椰果奶茶),用模板方法模式实现"流程复用+细节定制",对比"普通重复实现"和"模板方法实现"的差异,一看就懂、一练就会。

3.1 普通实现:重复代码的反例(痛点凸显)

若不使用模板方法模式,每种奶茶都编写完整的制作流程,会导致大量重复代码,后续新增奶茶类型(如烧仙草),需重复编写固定步骤,维护成本极高。

java 复制代码
// 珍珠奶茶类(完整流程,重复代码多)
public class PearlMilkTea {
    // 制作流程(固定步骤+细节)
    public void make() {
        boilTea(); // 固定步骤:煮茶
        addPearl(); // 可变细节:加珍珠
        shake(); // 固定步骤:摇匀
        cup(); // 固定步骤:装杯
    }

    // 固定步骤:煮茶
    private void boilTea() {
        System.out.println("煮制红茶基底");
    }

    // 可变细节:加珍珠
    private void addPearl() {
        System.out.println("加入黑珍珠");
    }

    // 固定步骤:摇匀
    private void shake() {
        System.out.println("摇匀奶茶");
    }

    // 固定步骤:装杯
    private void cup() {
        System.out.println("装入透明奶茶杯,完成\n");
    }
}

// 芋圆奶茶类(重复固定步骤,冗余严重)
public class TaroBallMilkTea {
    public void make() {
        boilTea(); // 重复代码
        addTaroBall(); // 可变细节:加芋圆
        shake(); // 重复代码
        cup(); // 重复代码
    }

    private void boilTea() {
        System.out.println("煮制红茶基底");
    }

    private void addTaroBall() {
        System.out.println("加入芋圆");
    }

    private void shake() {
        System.out.println("摇匀奶茶");
    }

    private void cup() {
        System.out.println("装入透明奶茶杯,完成\n");
    }
}

// 客户端调用
public class Client {
    public static void main(String[] args) {
        PearlMilkTea pearl = new PearlMilkTea();
        pearl.make();

        TaroBallMilkTea taro = new TaroBallMilkTea();
        taro.make();

        // 痛点:新增椰果奶茶,需重复编写boilTea、shake、cup方法
        // 代码冗余,维护成本高,修改固定步骤(如煮茶改为煮绿茶)需修改所有类
    }
}

【运行结果】:

text 复制代码
煮制红茶基底
加入黑珍珠
摇匀奶茶
装入透明奶茶杯,完成

煮制红茶基底
加入芋圆
摇匀奶茶
装入透明奶茶杯,完成

【缺点极其明显】:

  • 代码冗余严重:固定步骤(煮茶、摇匀、装杯)在每个类中重复编写,增加代码量;

  • 维护成本高:修改固定步骤(如煮茶改为煮绿茶),需修改所有子类,违背"开闭原则";

  • 扩展性差:新增奶茶类型,需重复编写所有固定步骤,开发效率低;

  • 可读性差:多个类的流程分散,难以快速梳理核心流程。

3.2 模板方法模式实现:流程复用的优雅代码(正例)

用模板方法模式重构奶茶制作流程,抽象父类定义流程模板和固定步骤,子类只需实现可变的"加配料"细节,无需重复编写固定步骤,新增奶茶类型只需新增子类,无需修改原有代码。

java 复制代码
// 1. 抽象模板角色:奶茶制作模板
public abstract class MilkTeaTemplate {
    // 模板方法:定义固定流程(核心,不可修改,子类直接调用)
    public final void make() {
        boilTea(); // 固定步骤1:煮茶
        addIngredient(); // 可变步骤:加配料(抽象方法,子类实现)
        shake(); // 固定步骤2:摇匀
        cup(); // 固定步骤3:装杯
    }

    // 固定步骤1:煮茶(父类实现,子类无需修改)
    protected void boilTea() {
        System.out.println("煮制红茶基底");
    }

    // 可变步骤:加配料(抽象方法,子类必须实现,定制细节)
    protected abstract void addIngredient();

    // 固定步骤2:摇匀(父类实现,子类无需修改)
    protected void shake() {
        System.out.println("摇匀奶茶");
    }

    // 固定步骤3:装杯(父类实现,子类无需修改)
    protected void cup() {
        System.out.println("装入透明奶茶杯,完成\n");
    }
}

// 2. 具体模板角色1:珍珠奶茶(实现可变步骤)
public class PearlMilkTea extends MilkTeaTemplate {
    @Override
    protected void addIngredient() {
        System.out.println("加入黑珍珠"); // 珍珠奶茶的定制细节
    }
}

// 2. 具体模板角色2:芋圆奶茶(实现可变步骤)
public class TaroBallMilkTea extends MilkTeaTemplate {
    @Override
    protected void addIngredient() {
        System.out.println("加入芋圆"); // 芋圆奶茶的定制细节
    }
}

// 2. 具体模板角色3:椰果奶茶(新增,无需修改原有代码)
public class CoconutMilkTea extends MilkTeaTemplate {
    @Override
    protected void addIngredient() {
        System.out.println("加入椰果"); // 椰果奶茶的定制细节
    }
}

// 3. 客户端调用(模板方法模式实现)
public class Client {
    public static void main(String[] args) {
        // 珍珠奶茶
        MilkTeaTemplate pearl = new PearlMilkTea();
        pearl.make();

        // 芋圆奶茶
        MilkTeaTemplate taro = new TaroBallMilkTea();
        taro.make();

        // 新增椰果奶茶,无需修改原有任何代码
        MilkTeaTemplate coconut = new CoconutMilkTea();
        coconut.make();

        // 修改固定步骤(如煮茶改为煮绿茶),只需修改抽象父类的boilTea方法
    }
}

【运行结果】:

text 复制代码
煮制红茶基底
加入黑珍珠
摇匀奶茶
装入透明奶茶杯,完成

煮制红茶基底
加入芋圆
摇匀奶茶
装入透明奶茶杯,完成

煮制红茶基底
加入椰果
摇匀奶茶
装入透明奶茶杯,完成

【代码优势极其明显】:

  • 复用性极强:固定步骤在父类中统一实现,子类无需重复编写,极大减少代码冗余;

  • 扩展性极强:新增奶茶类型,只需新增子类实现抽象方法,无需修改原有代码,符合"开闭原则";

  • 维护成本低:修改固定步骤,只需修改抽象父类的对应方法,所有子类自动复用修改后的逻辑;

  • 流程清晰:抽象父类统一管理核心流程,子类仅关注自身的定制细节,可读性和可维护性大幅提升;

  • 规范流程:模板方法固定了步骤顺序,避免子类随意修改流程逻辑,保证业务一致性。

【核心总结】:模板方法模式的核心不是"新增功能",而是"流程复用与规范"------通过抽象父类定义固定流程模板,将可变细节延迟到子类实现,既保证了流程的一致性,又实现了细节的灵活性,解决了相似流程的重复开发问题。

四、模板方法模式实战(真实业务场景,可直接复用)

结合Java后端最常见的"订单处理系统"场景,用模板方法模式实现"不同类型订单的流程复用与细节定制",贴合真实项目开发(Spring Boot环境),代码可直接复制到项目中使用,解决实际开发中的流程冗余痛点。

4.1 实战场景说明

场景:电商系统有3种核心订单类型(普通订单、秒杀订单、预售订单),核心处理流程一致,仅部分细节不同:

  • 核心流程(固定):校验订单 → 扣减库存 → 生成订单 → 通知用户;

  • 可变细节:

    • 普通订单:校验订单(校验库存是否充足)、通知用户(短信通知);

    • 秒杀订单:校验订单(校验秒杀资格、库存是否充足)、通知用户(短信+APP推送);

    • 预售订单:校验订单(校验预售资格、支付定金)、通知用户(短信+公众号推送)。

要求:用模板方法模式实现订单处理逻辑,支持流程复用,新增订单类型(如团购订单)时,无需修改原有流程代码,只需新增具体模板子类,贴合Spring Boot实战规范,支持异常统一处理。

  1. 抽象模板:订单处理模板(定义核心流程,实现固定步骤,声明可变步骤);

  2. 具体模板:普通订单、秒杀订单、预售订单处理类(实现各自的可变细节);

  3. 实战亮点:结合Spring依赖注入、统一异常处理,实现订单处理的规范化和可扩展性,贴合真实项目开发。

4.2 实战代码实现(Spring Boot环境,可直接复用)

java 复制代码
// 1. 公共实体:订单实体类
@Data
public class Order {
    private Long orderId;
    private String orderType; // 订单类型:normal(普通)、seckill(秒杀)、presale(预售)
    private Long userId;
    private Double amount;
    private Integer stock; // 商品库存
    private Boolean isQualified; // 是否具备购买资格(秒杀/预售专用)
    private Boolean hasPaidDeposit; // 是否支付定金(预售专用)
}

// 2. 公共实体:响应结果封装
@Data
public class Result {
    private Integer code; // 200成功,500失败
    private String message;
    private Object data;

    public static Result success(String message, Object data) {
        Result result = new Result();
        result.setCode(200);
        result.setMessage(message);
        result.setData(data);
        return result;
    }

    public static Result fail(String message) {
        Result result = new Result();
        result.setCode(500);
        result.setMessage(message);
        return result;
    }
}

// 3. 抽象模板角色:订单处理模板
@Component
public abstract class OrderHandlerTemplate {
    // 模板方法:核心订单处理流程(final修饰,防止子类修改流程顺序)
    public final Result handleOrder(Order order) {
        try {
            // 固定步骤1:校验订单(可变细节,子类实现)
            boolean validate = validateOrder(order);
            if (!validate) {
                return Result.fail("订单校验失败");
            }

            // 固定步骤2:扣减库存(父类实现,所有订单统一逻辑)
            deductStock(order);

            // 固定步骤3:生成订单(父类实现,所有订单统一逻辑)
            Order generatedOrder = generateOrder(order);

            // 固定步骤4:通知用户(可变细节,子类实现)
            notifyUser(generatedOrder);

            return Result.success("订单处理成功", generatedOrder);
        } catch (Exception e) {
            return Result.fail("订单处理失败:" + e.getMessage());
        }
    }

    // 可变步骤1:校验订单(抽象方法,子类实现具体校验逻辑)
    protected abstract boolean validateOrder(Order order);

    // 固定步骤2:扣减库存(所有订单统一逻辑,父类实现)
    protected void deductStock(Order order) {
        if (order.getStock() <= 0) {
            throw new RuntimeException("库存不足");
        }
        order.setStock(order.getStock() - 1);
        System.out.println("库存扣减成功,剩余库存:" + order.getStock());
    }

    // 固定步骤3:生成订单(所有订单统一逻辑,父类实现,简化生成订单ID)
    protected Order generateOrder(Order order) {
        order.setOrderId(System.currentTimeMillis()); // 用时间戳作为订单ID
        System.out.println("订单生成成功,订单ID:" + order.getOrderId());
        return order;
    }

    // 可变步骤2:通知用户(抽象方法,子类实现具体通知逻辑)
    protected abstract void notifyUser(Order order);
}

// 4. 具体模板角色1:普通订单处理类
@Component("normalOrderHandler")
public class NormalOrderHandler extends OrderHandlerTemplate {
    @Override
    protected boolean validateOrder(Order order) {
        // 普通订单校验:仅校验库存
        System.out.println("普通订单校验:校验库存");
        return order.getStock() > 0;
    }

    @Override
    protected void notifyUser(Order order) {
        // 普通订单通知:短信通知
        System.out.println("普通订单通知:向用户" + order.getUserId() + "发送短信,通知订单处理成功\n");
    }
}

// 4. 具体模板角色2:秒杀订单处理类
@Component("seckillOrderHandler")
public class SeckillOrderHandler extends OrderHandlerTemplate {
    @Override
    protected boolean validateOrder(Order order) {
        // 秒杀订单校验:校验库存 + 校验秒杀资格
        System.out.println("秒杀订单校验:校验库存 + 校验秒杀资格");
        return order.getStock() > 0 && order.getIsQualified();
    }

    @Override
    protected void notifyUser(Order order) {
        // 秒杀订单通知:短信 + APP推送
        System.out.println("秒杀订单通知:向用户" + order.getUserId() + "发送短信和APP推送,通知订单处理成功\n");
    }
}

// 4. 具体模板角色3:预售订单处理类
@Component("presaleOrderHandler")
public class PresaleOrderHandler extends OrderHandlerTemplate {
    @Override
    protected boolean validateOrder(Order order) {
        // 预售订单校验:校验预售资格 + 校验定金支付 + 校验库存
        System.out.println("预售订单校验:校验预售资格 + 校验定金支付 + 校验库存");
        return order.getStock() > 0 && order.getIsQualified() && order.getHasPaidDeposit();
    }

    @Override
    protected void notifyUser(Order order) {
        // 预售订单通知:短信 + 公众号推送
        System.out.println("预售订单通知:向用户" + order.getUserId() + "发送短信和公众号推送,通知订单处理成功\n");
    }
}

// 5. 订单服务层:统一调度订单处理(Spring Bean)
@Service
public class OrderService {
    // 注入所有订单处理模板(key:订单类型对应的bean名称,value:模板对象)
    private final Map<String, OrderHandlerTemplate> orderHandlerMap;

    // 构造方法注入(Spring自动将所有OrderHandlerTemplate实现类注入到map中)
    public OrderService(Map<String, OrderHandlerTemplate> orderHandlerMap) {
        this.orderHandlerMap = orderHandlerMap;
    }

    // 统一订单处理入口:根据订单类型选择对应的模板
    public Result processOrder(Order order) {
        // 根据订单类型获取对应的处理模板
        String handlerBeanName = order.getOrderType() + "OrderHandler";
        OrderHandlerTemplate handler = orderHandlerMap.get(handlerBeanName);
        if (handler == null) {
            return Result.fail("不支持该类型订单");
        }
        // 调用模板方法,执行订单处理流程
        return handler.handleOrder(order);
    }
}

// 6. 控制层:接口对外提供访问(Spring Boot Controller)
@RestController
@RequestMapping("/order")
public class OrderController {
    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping("/process")
    public Result processOrder(@RequestBody Order order) {
        return orderService.processOrder(order);
    }
}

// 7. 测试类(模拟接口调用,Spring Boot环境可直接注入测试)
public class OrderTest {
    public static void main(String[] args) {
        // 模拟Spring容器注入
        OrderHandlerTemplate normalHandler = new NormalOrderHandler();
        OrderHandlerTemplate seckillHandler = new SeckillOrderHandler();
        OrderHandlerTemplate presaleHandler = new PresaleOrderHandler();

        // 初始化订单处理map
        Map<String, OrderHandlerTemplate> handlerMap = new HashMap<>();
        handlerMap.put("normalOrderHandler", normalHandler);
        handlerMap.put("seckillOrderHandler", seckillHandler);
        handlerMap.put("presaleOrderHandler", presaleHandler);

        // 初始化服务
        OrderService orderService = new OrderService(handlerMap);

        // 测试普通订单
        Order normalOrder = new Order();
        normalOrder.setOrderType("normal");
        normalOrder.setUserId(1001L);
        normalOrder.setAmount(99.0);
        normalOrder.setStock(10);
        System.out.println(orderService.processOrder(normalOrder));

        // 测试秒杀订单
        Order seckillOrder = new Order();
        seckillOrder.setOrderType("seckill");
        seckillOrder.setUserId(1002L);
        seckillOrder.setAmount(59.0);
        seckillOrder.setStock(5);
        seckillOrder.setIsQualified(true);
        System.out.println(orderService.processOrder(seckillOrder));

        // 测试预售订单
        Order presaleOrder = new Order();
        presaleOrder.setOrderType("presale");
        presaleOrder.setUserId(1003L);
        presaleOrder.setAmount(199.0);
        presaleOrder.setStock(3);
        presaleOrder.setIsQualified(true);
        presaleOrder.setHasPaidDeposit(true);
        System.out.println(orderService.processOrder(presaleOrder));
    }
}

【运行结果】:

text 复制代码
普通订单校验:校验库存
库存扣减成功,剩余库存:9
订单生成成功,订单ID:1713349260000
普通订单通知:向用户1001发送短信,通知订单处理成功
Result(code=200, message=订单处理成功, data=Order(orderId=1713349260000, orderType=normal, userId=1001, amount=99.0, stock=9, isQualified=null, hasPaidDeposit=null))

秒杀订单校验:校验库存 + 校验秒杀资格
库存扣减成功,剩余库存:4
订单生成成功,订单ID:1713349260001
秒杀订单通知:向用户1002发送短信和APP推送,通知订单处理成功
Result(code=200, message=订单处理成功, data=Order(orderId=1713349260001, orderType=seckill, userId=1002, amount=59.0, stock=4, isQualified=true, hasPaidDeposit=null))

预售订单校验:校验预售资格 + 校验定金支付 + 校验库存
库存扣减成功,剩余库存:2
订单生成成功,订单ID:1713349260002
预售订单通知:向用户1003发送短信和公众号推送,通知订单处理成功
Result(code=200, message=订单处理成功, data=Order(orderId=1713349260002, orderType=presale, userId=1003, amount=199.0, stock=2, isQualified=true, hasPaidDeposit=true))

【实战亮点】:

  • 贴合Spring Boot实战:使用@Component、@Service、@RestController等注解,符合真实项目开发规范,可直接复制复用;

  • 流程复用彻底:固定的订单处理步骤(扣减库存、生成订单)在抽象父类统一实现,子类仅关注自身的校验和通知细节;

  • 扩展性极强:新增订单类型(如团购订单),只需新增子类继承OrderHandlerTemplate,实现抽象方法,无需修改原有服务和控制器代码;

  • 规范统一:模板方法固定了订单处理的步骤顺序,避免子类随意修改流程,保证所有订单处理逻辑的一致性;

  • 异常统一处理:模板方法中统一捕获异常,返回标准化响应,贴合接口开发的实际需求。

补充:真实项目中,可结合自定义注解(如@OrderType(&#34;normal&#34;)),简化订单类型与模板的映射,无需手动拼接bean名称,进一步提升开发效率。

五、模板方法模式在JDK/框架中的应用(面试必提)

模板方法模式的核心价值是"流程复用与规范",这也是它被广泛应用在JDK源码和主流Java框架中的原因,掌握这些应用场景,面试时能加分不少,还能帮助你理解框架底层设计思想。

5.1 JDK 中的模板方法模式(最常见,面试高频)

JDK中有多个经典的模板方法模式应用,其中AbstractList、InputStream是面试必问考点,一定要掌握:

5.1.1 AbstractList 抽象类(最典型)

Java中的AbstractList(抽象列表),是模板方法模式的标准实现,定义了列表操作的核心流程,将可变细节延迟到子类实现:

  • 抽象模板(Abstract Template):AbstractList抽象类,定义了get(int index)、add(int index, E element)等模板方法,封装了列表操作的固定流程;

  • 具体模板(Concrete Template):ArrayList、LinkedList等子类,实现了AbstractList中的抽象方法(如size()、get(int index)),定制列表的具体实现细节;

  • 核心逻辑:AbstractList定义了列表操作的统一流程,子类只需实现抽象方法,即可完成列表的具体功能,无需重复编写流程代码。

java 复制代码
// AbstractList中的模板方法示例(简化)
public abstract class AbstractList<E> implements List<E> {
    // 模板方法:add方法,定义固定流程
    public boolean add(E e) {
        add(size(), e); // 调用另一个模板方法
        return true;
    }

    // 模板方法:add(int index, E element),定义固定流程
    public void add(int index, E element) {
        throw new UnsupportedOperationException(); // 默认实现,子类可重写
    }

    // 抽象方法:size(),可变细节,子类必须实现
    public abstract int size();

    // 抽象方法:get(int index),可变细节,子类必须实现
    public abstract E get(int index);
}

// 具体模板:ArrayList,实现抽象方法
public class ArrayList<E> extends AbstractList<E> {
    private Object[] elementData;
    private int size;

    @Override
    public int size() {
        return size; //  ArrayList的size实现
    }

    @Override
    public E get(int index) {
        rangeCheck(index); // 校验索引(固定逻辑)
        return elementData(index); // 返回对应元素(ArrayList的具体实现)
    }

    @Override
    public void add(int index, E element) {
        // 实现add的具体逻辑(扩容、移位、赋值)
        ensureCapacityInternal(size + 1);
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = element;
        size++;
    }
}

核心逻辑:AbstractList定义了列表操作的模板流程,ArrayList、LinkedList等子类通过实现抽象方法,定制自身的存储和操作细节,实现了流程复用,同时保证了列表操作的统一性。

5.1.2 InputStream 抽象类

Java中的InputStream(输入流),底层采用模板方法模式实现,定义了输入流读取的核心流程:

  • 抽象模板(Abstract Template):InputStream抽象类,定义了read()、read(byte[] b)等模板方法,封装了读取数据的固定流程;

  • 具体模板(Concrete Template):FileInputStream、ByteArrayInputStream等子类,实现了InputStream中的抽象方法(如read()),定制不同输入流的读取细节;

  • 核心逻辑:InputStream定义了"读取数据"的统一流程,子类只需实现具体的读取逻辑,即可实现不同类型的输入流操作,无需重复编写流程代码。

5.2 框架中的模板方法模式

5.2.1 Spring 中的 AbstractApplicationContext

Spring框架的AbstractApplicationContext(抽象应用上下文),是模板方法模式的典型应用,定义了Spring容器初始化的核心流程:

  • 抽象模板(Abstract Template):AbstractApplicationContext抽象类,定义了refresh()模板方法,封装了容器初始化的固定步骤(如准备刷新、获取BeanFactory、初始化BeanFactory等);

  • 具体模板(Concrete Template):ClassPathXmlApplicationContext、AnnotationConfigApplicationContext等子类,实现了抽象方法,定制不同类型应用上下文的初始化细节;

  • 核心逻辑:refresh()方法固定了Spring容器初始化的流程,子类只需实现抽象方法,即可完成不同类型容器的初始化,实现流程复用。

5.2.2 MyBatis 中的 BaseExecutor

MyBatis的BaseExecutor(基础执行器),底层采用模板方法模式实现,定义了SQL执行的核心流程:

  • 抽象模板(Abstract Template):BaseExecutor抽象类,定义了query()、update()等模板方法,封装了SQL执行的固定流程(如获取连接、创建Statement、执行SQL、处理结果等);

  • 具体模板(Concrete Template):SimpleExecutor、ReuseExecutor等子类,实现了抽象方法,定制不同执行器的SQL执行细节;

  • 核心逻辑:BaseExecutor定义了SQL执行的统一流程,子类只需实现具体的执行逻辑,即可实现不同类型的SQL执行方式,提升代码复用性。

六、模板方法模式面试高频考点(必背,避坑)

模板方法模式是Java后端面试的高频考点(中高级岗位尤为突出),重点考察"核心思想""核心角色""JDK应用""与其他模式的区别",记住以下考点,轻松应对面试。

1. 模板方法模式的核心作用是什么?(高频)

核心答案(一句话记住,面试直接说):定义固定的业务流程模板,将流程中的可变细节延迟到子类实现,实现流程复用和规范,同时保证扩展性,符合开闭原则

补充:模板方法模式解决的是"相似流程重复开发"的问题,核心是"流程复用与规范",而非"算法切换"或"功能增强"。

2. 模板方法模式的核心角色有哪些?(必背)

核心答案(一句话记住):抽象模板(定义流程模板和固定步骤,声明抽象方法)、具体模板(继承抽象模板,实现抽象方法,定制细节)

角色名称 核心职责 示例
抽象模板 定义流程模板(模板方法),实现固定步骤,声明抽象的可变步骤,规范流程顺序 MilkTeaTemplate、AbstractList
具体模板 继承抽象模板,实现抽象方法,定制流程中的可变细节,不修改模板方法 PearlMilkTea、ArrayList

3. 模板方法模式和策略模式的区别?(高频,必背)

很多面试官会把这两个行为型模式放在一起问,核心区别(一句话区分):模板方法模式是"固定流程+可变细节",通过继承实现,流程不可变、细节可变;策略模式是"算法动态切换",通过组合实现,算法平等可完全替换

对比维度 模板方法模式 策略模式
核心目的 复用固定流程,定制细节,规范流程顺序 动态切换不同算法,算法可完全替换
实现方式 继承(子类继承抽象模板,实现抽象方法) 组合(上下文持有抽象策略引用,动态切换)
流程/算法关系 流程固定,细节可变,细节依赖于流程 算法平等,无依赖关系,可自由切换
灵活性 灵活性低,流程不可修改,仅能定制细节 灵活性高,可动态切换算法,新增算法无需修改原有代码
使用场景 相似流程,核心步骤固定,细节不同(如订单处理、数据导出) 多种相似算法,需动态切换(如支付方式、排序方式)

4. 模板方法模式的优缺点是什么?(必背)

核心答案(简洁好记,面试直接说):

  • 优点:流程复用性高(固定步骤统一实现)、流程规范(模板方法固定步骤顺序)、扩展性强(新增细节只需新增子类)、维护成本低(修改固定步骤只需修改父类);

  • 缺点:灵活性有限(流程不可修改,仅能定制细节)、子类数量增多(每种细节对应一个子类)、继承关系耦合(子类依赖抽象父类)。

5. 如何解决模板方法模式"子类数量过多"的问题?(进阶考点)

核心答案(面试加分):可结合"工厂模式"或"注解+反射"优化,由工厂类统一管理具体模板子类的创建和获取,客户端无需直接创建子类对象,只需传入细节标识,由工厂返回对应子类;也可通过"钩子方法"减少子类数量,将部分可选细节用钩子方法实现,子类无需强制实现所有细节。

补充:钩子方法(Hook Method)是模板方法模式的扩展,在抽象模板中定义默认实现的方法,子类可根据需求重写,用于控制流程的执行逻辑(如判断某个步骤是否需要执行),减少子类的实现成本。

七、总结(新手必看)

模板方法模式的核心很简单:定好流程框架,把不变的步骤写在父类,把可变的细节留给子类,既保证流程统一,又能灵活定制,还能减少重复代码

对于新手来说,掌握模板方法模式的关键是"区分抽象模板和具体模板",理解"模板方法固定流程、抽象方法定制细节"的思想------先学会用模板方法模式优化简单的相似流程(如奶茶制作、简单订单处理),再结合Spring Boot实战案例,理解框架中的模板方法应用;对于中高级开发者,重点掌握模板方法与策略模式的区别,以及框架底层的模板方法设计,面试时才能从容应对。

最后记住:模板方法模式不是"万能的",它只适用于"核心流程固定、细节可变"的场景,若流程不固定、需要频繁修改流程顺序,不适合使用(此时更适合策略模式)。合理使用模板方法模式,能让你的代码更简洁、更规范、更易维护,这也是后端开发者从"会写代码"到"会写好代码"的关键一步。

相关推荐
2501_914245932 小时前
如何用 setCustomValidity 自定义表单验证失败的提示文本
jvm·数据库·python
2301_817672262 小时前
bootstrap如何修改标签页切换的过渡时间
jvm·数据库·python
沐雪轻挽萤2 小时前
17. C++17新特性-并行算法 (Parallel Algorithms)
java·开发语言·c++
覆东流2 小时前
第3天:Python print深入与格式化输出
开发语言·后端·python
蓝色的杯子2 小时前
JWT 到底怎么用?一篇讲透 + FastAPI 鉴权实战
python·fastapi·jwt
zhangchaoxies2 小时前
JavaScript中Tree-shaking失效的场景及其优化对策
jvm·数据库·python
2501_914245932 小时前
SQL在GROUP BY中如何保留非聚合列_配合ANY_VALUE或窗口函数
jvm·数据库·python
A7bert7772 小时前
【YOLOv8部署至RDK X5】模型训练→转换bin→Sunrise 5部署
c++·人工智能·python·深度学习·yolo·机器学习
weixin_580614002 小时前
如何防止SQL注入篡改数据_实施双重身份验证与授权
jvm·数据库·python