【从零入门23种设计模式23】行为型之模板模式

一、模板方法模式核心定义

模板方法模式是行为型设计模式的一种,核心目的是:

定义一个算法的骨架(固定流程),将算法中某些步骤的具体实现延迟到子类中;子类可以在不改变算法整体结构的前提下,重写算法中的特定步骤。

简单来说:把业务流程的 "固定部分" 封装在父类的模板方法中,"可变部分" 留给子类实现,既保证流程统一,又支持步骤定制

核心解决的问题
  1. 复用固定流程代码 :将多个子类共有的固定流程(如订单下单、支付、发货)提取到父类,避免代码重复;
  2. 标准化业务流程 :强制子类遵循统一的流程骨架,防止流程混乱(如必须先校验参数,再执行业务,最后记录日志);
  3. 扩展可变步骤 :子类可自定义流程中的可变步骤,无需修改父类的核心流程;
  4. 控制流程执行 :父类可通过 "钩子方法" 控制是否执行某个步骤,或调整步骤执行顺序;
  5. 符合开闭原则 :新增业务场景只需新增子类,重写可变步骤,无需修改父类流程。
生活类比
  • 场景 1 :泡茶 / 泡咖啡
    • 固定流程(模板方法):烧开水 → 准备杯子 → 放茶叶 / 咖啡粉 → 冲开水 → 加调料(可选)→ 饮用;
    • 可变步骤:放茶叶 / 咖啡粉(子类实现)、加调料(茶加冰糖,咖啡加牛奶 / 糖);
    • 核心:父类定义 "冲泡饮品" 的固定流程,子类(泡茶 / 泡咖啡)只实现自己的特有步骤。
  • 场景 2 :考试答题
    • 固定流程:拿到试卷 → 填写姓名 / 考号 → 做选择题 → 做填空题 → 做简答题 → 交卷;
    • 可变步骤:各题型的答题内容(不同考生实现不同);
    • 核心:考试流程固定,答题内容可变。
  • 场景 3 :电商订单处理
    • 固定流程:校验订单 → 扣减库存 → 生成支付单 → 记录订单日志 → 返回结果;
    • 可变步骤:扣减库存(普通订单 / 秒杀订单逻辑不同)、生成支付单(不同支付方式);
    • 核心:订单处理流程统一,库存扣减 / 支付生成可定制。
标准角色
角色 职责 类比(泡茶 / 咖啡场景) 代码定位
抽象模板(AbstractClass) 定义算法骨架(模板方法),包含:1. 模板方法:固定流程(如makeDrink());2. 抽象方法:可变步骤(子类实现);3. 具体方法:固定步骤(父类实现);4. 钩子方法:可选步骤(子类可重写) 饮品抽象类(Drink) 抽象类(核心)
具体模板(ConcreteClass) 实现抽象模板中的抽象方法(可变步骤),可选重写钩子方法,不修改模板方法的流程 泡茶类(Tea)、泡咖啡类(Coffee) 子类(业务实现)
核心 UML 类图

二、泡茶 / 泡咖啡

以 "泡茶 / 泡咖啡" 为例,实现模板方法模式的核心逻辑 ------ 父类定义固定冲泡流程,子类实现各自的可变步骤,体现 "固定流程 + 可变步骤" 的核心思想。

1. 步骤 1:定义抽象模板(饮品抽象类)
复制代码
/**
 * 抽象模板:饮品抽象类(定义冲泡饮品的固定流程)
 */
public abstract class AbstractDrink {
    /**
     * 模板方法:冲泡饮品的核心流程(final修饰,防止子类重写流程)
     */
    public final void makeDrink() {
        // 固定步骤1:烧开水
        boilWater();
        // 固定步骤2:准备杯子
        prepareCup();
        // 可变步骤3:放原料(茶叶/咖啡粉)→ 子类实现
        addMaterial();
        // 固定步骤4:冲开水
        pourWater();
        // 钩子方法:是否加调料(可选步骤)
        if (needCondiment()) {
            // 可变步骤5:加调料 → 子类实现
            addCondiment();
        }
        // 固定步骤6:饮用
        drink();
    }

    // ---------------------- 固定步骤(父类实现) ----------------------
    /**
     * 固定步骤:烧开水
     */
    private void boilWater() {
        System.out.println("【固定步骤】烧开水(100℃)");
    }

    /**
     * 固定步骤:准备杯子
     */
    private void prepareCup() {
        System.out.println("【固定步骤】准备干净的杯子");
    }

    /**
     * 固定步骤:冲开水
     */
    private void pourWater() {
        System.out.println("【固定步骤】向杯子中冲入开水");
    }

    /**
     * 固定步骤:饮用
     */
    private void drink() {
        System.out.println("【固定步骤】饮品制作完成,可以饮用");
    }

    // ---------------------- 可变步骤(子类实现) ----------------------
    /**
     * 抽象方法:添加原料(茶叶/咖啡粉)→ 子类实现
     */
    protected abstract void addMaterial();

    /**
     * 抽象方法:添加调料(冰糖/牛奶)→ 子类实现
     */
    protected abstract void addCondiment();

    // ---------------------- 钩子方法(可选重写) ----------------------
    /**
     * 钩子方法:是否需要加调料(默认需要,子类可重写)
     */
    protected boolean needCondiment() {
        return true;
    }
}
2. 步骤 2:实现具体模板(泡茶类)
复制代码
/**
 * 具体模板:泡茶类(实现可变步骤,重写钩子方法)
 */
public class Tea extends AbstractDrink {
    @Override
    protected void addMaterial() {
        System.out.println("【可变步骤】向杯子中加入龙井茶叶");
    }

    @Override
    protected void addCondiment() {
        System.out.println("【可变步骤】向茶中加入冰糖");
    }

    // 可选:重写钩子方法(比如无糖茶不需要加调料)
    // @Override
    // protected boolean needCondiment() {
    //     return false;
    // }
}
3. 步骤 3:实现具体模板(泡咖啡类)
复制代码
/**
 * 具体模板:泡咖啡类(实现可变步骤)
 */
public class Coffee extends AbstractDrink {
    @Override
    protected void addMaterial() {
        System.out.println("【可变步骤】向杯子中加入蓝山咖啡粉");
    }

    @Override
    protected void addCondiment() {
        System.out.println("【可变步骤】向咖啡中加入牛奶和方糖");
    }
}
4. 客户端(测试冲泡饮品)
复制代码
/**
 * 客户端:测试模板方法模式(泡茶/泡咖啡)
 */
public class TemplateClient {
    public static void main(String[] args) {
        // 1. 泡茶
        System.out.println("======= 制作茶 =======");
        AbstractDrink tea = new Tea();
        tea.makeDrink();

        // 2. 泡咖啡
        System.out.println("\n======= 制作咖啡 =======");
        AbstractDrink coffee = new Coffee();
        coffee.makeDrink();
    }
}
输出结果
复制代码
======= 制作茶 =======
【固定步骤】烧开水(100℃)
【固定步骤】准备干净的杯子
【可变步骤】向杯子中加入龙井茶叶
【固定步骤】向杯子中冲入开水
【可变步骤】向茶中加入冰糖
【固定步骤】饮品制作完成,可以饮用

======= 制作咖啡 =======
【固定步骤】烧开水(100℃)
【固定步骤】准备干净的杯子
【可变步骤】向杯子中加入蓝山咖啡粉
【固定步骤】向杯子中冲入开水
【可变步骤】向咖啡中加入牛奶和方糖
【固定步骤】饮品制作完成,可以饮用

三、Spring 实战版(订单处理流程)

在业务开发中,模板方法模式最核心的实战场景是订单处理流程标准化------ 父类定义 "订单创建→参数校验→库存扣减→支付生成→日志记录" 的固定流程,子类(普通订单 / 秒杀订单)只实现各自的库存扣减逻辑,既保证流程统一,又支持业务定制。

1. 依赖准备(Spring Boot)
复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>3.2.3</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
2. 核心模型定义
复制代码
import lombok.Data;

/**
 * 订单DTO
 */
@Data
public class OrderDTO {
    private String orderId; // 订单ID
    private String productId; // 商品ID
    private int quantity; // 购买数量
    private double amount; // 订单金额
    private String orderType; // 订单类型(NORMAL/SECKILL)
}

/**
 * 订单处理结果
 */
@Data
@lombok.AllArgsConstructor
public class OrderResult {
    private boolean success; // 是否成功
    private String message; // 结果信息
    private OrderDTO order; // 订单信息
}
3. 抽象模板(订单处理抽象类)
复制代码
import lombok.extern.slf4j.Slf4j;

/**
 * 抽象模板:订单处理抽象类(定义订单处理的固定流程)
 */
@Slf4j
public abstract class AbstractOrderHandler {
    /**
     * 模板方法:订单处理核心流程(final修饰,防止子类修改流程)
     */
    public final OrderResult handleOrder(OrderDTO order) {
        try {
            // 固定步骤1:参数校验
            validateParams(order);
            // 固定步骤2:记录订单开始日志
            logStart(order);
            // 可变步骤3:扣减库存(子类实现)
            deductStock(order);
            // 固定步骤4:生成支付单
            createPayOrder(order);
            // 钩子方法:是否发送通知(可选)
            if (needSendNotify()) {
                // 可变步骤5:发送通知(子类实现)
                sendNotify(order);
            }
            // 固定步骤6:记录订单完成日志
            logComplete(order);
            return new OrderResult(true, "订单处理成功", order);
        } catch (Exception e) {
            log.error("订单处理失败 | 订单ID:{},原因:{}", order.getOrderId(), e.getMessage());
            return new OrderResult(false, "订单处理失败:" + e.getMessage(), order);
        }
    }

    // ---------------------- 固定步骤(父类实现) ----------------------
    /**
     * 固定步骤:参数校验
     */
    private void validateParams(OrderDTO order) {
        log.info("【固定步骤】校验订单参数 | 订单ID:{}", order.getOrderId());
        if (order.getProductId() == null || order.getProductId().isEmpty()) {
            throw new IllegalArgumentException("商品ID不能为空");
        }
        if (order.getQuantity() <= 0) {
            throw new IllegalArgumentException("购买数量必须大于0");
        }
        log.info("【固定步骤】参数校验通过 | 订单ID:{}", order.getOrderId());
    }

    /**
     * 固定步骤:记录开始日志
     */
    private void logStart(OrderDTO order) {
        log.info("【固定步骤】订单处理开始 | 订单ID:{},类型:{}", order.getOrderId(), order.getOrderType());
    }

    /**
     * 固定步骤:生成支付单
     */
    private void createPayOrder(OrderDTO order) {
        log.info("【固定步骤】生成支付单 | 订单ID:{},金额:{}元", order.getOrderId(), order.getAmount());
        // 模拟生成支付单逻辑
    }

    /**
     * 固定步骤:记录完成日志
     */
    private void logComplete(OrderDTO order) {
        log.info("【固定步骤】订单处理完成 | 订单ID:{}", order.getOrderId());
    }

    // ---------------------- 可变步骤(子类实现) ----------------------
    /**
     * 抽象方法:扣减库存(普通订单/秒杀订单逻辑不同)
     */
    protected abstract void deductStock(OrderDTO order);

    /**
     * 抽象方法:发送通知(短信/推送,子类实现)
     */
    protected abstract void sendNotify(OrderDTO order);

    // ---------------------- 钩子方法(可选重写) ----------------------
    /**
     * 钩子方法:是否需要发送通知(默认需要)
     */
    protected boolean needSendNotify() {
        return true;
    }
}
4. 具体模板(普通订单处理器)
复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 具体模板:普通订单处理器(实现可变步骤)
 */
@Slf4j
@Component
public class NormalOrderHandler extends AbstractOrderHandler {
    @Override
    protected void deductStock(OrderDTO order) {
        log.info("【可变步骤-普通订单】扣减普通库存 | 订单ID:{},商品ID:{},数量:{}",
                order.getOrderId(), order.getProductId(), order.getQuantity());
        // 模拟普通库存扣减逻辑(直接扣减)
    }

    @Override
    protected void sendNotify(OrderDTO order) {
        log.info("【可变步骤-普通订单】发送短信通知 | 订单ID:{}", order.getOrderId());
        // 模拟发送短信逻辑
    }
}
5. 具体模板(秒杀订单处理器)
复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 具体模板:秒杀订单处理器(实现可变步骤,重写钩子方法)
 */
@Slf4j
@Component
public class SeckillOrderHandler extends AbstractOrderHandler {
    @Override
    protected void deductStock(OrderDTO order) {
        log.info("【可变步骤-秒杀订单】扣减秒杀库存(加分布式锁) | 订单ID:{},商品ID:{},数量:{}",
                order.getOrderId(), order.getProductId(), order.getQuantity());
        // 模拟秒杀库存扣减逻辑(加锁、校验库存上限)
        if (order.getQuantity() > 1) {
            throw new IllegalArgumentException("秒杀订单限购1件");
        }
    }

    @Override
    protected void sendNotify(OrderDTO order) {
        log.info("【可变步骤-秒杀订单】发送APP推送通知 | 订单ID:{}", order.getOrderId());
        // 模拟发送APP推送逻辑
    }

    // 重写钩子方法:秒杀订单强制发送通知(也可改为false关闭)
    @Override
    protected boolean needSendNotify() {
        return true;
    }
}
6. 订单服务(业务入口)
复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * 订单服务(根据订单类型选择处理器)
 */
@Slf4j
@Service
public class OrderService {
    // 处理器映射:订单类型 → 处理器
    private final Map<String, AbstractOrderHandler> handlerMap = new HashMap<>();

    /**
     * 注入所有订单处理器(Spring自动扫描)
     */
    @Resource
    public OrderService(NormalOrderHandler normalOrderHandler, SeckillOrderHandler seckillOrderHandler) {
        handlerMap.put("NORMAL", normalOrderHandler);
        handlerMap.put("SECKILL", seckillOrderHandler);
    }

    /**
     * 处理订单(根据类型选择处理器)
     */
    public OrderResult processOrder(OrderDTO order) {
        log.info("【订单服务】开始处理订单 | 订单ID:{},类型:{}", order.getOrderId(), order.getOrderType());
        AbstractOrderHandler handler = handlerMap.get(order.getOrderType());
        if (handler == null) {
            return new OrderResult(false, "不支持的订单类型:" + order.getOrderType(), order);
        }
        return handler.handleOrder(order);
    }
}
7. 客户端(Spring Boot 测试)
复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * 客户端:测试Spring版模板方法模式
 */
@SpringBootApplication
public class SpringTemplateDemoApplication {
    public static void main(String[] args) {
        // 1. 启动Spring容器
        ConfigurableApplicationContext context = SpringApplication.run(SpringTemplateDemoApplication.class, args);
        OrderService orderService = context.getBean(OrderService.class);

        // 2. 测试1:处理普通订单
        System.out.println("======= 测试1:处理普通订单 =======");
        OrderDTO normalOrder = new OrderDTO();
        normalOrder.setOrderId("O20240317001");
        normalOrder.setProductId("P1001");
        normalOrder.setQuantity(2);
        normalOrder.setAmount(199.8);
        normalOrder.setOrderType("NORMAL");
        OrderResult normalResult = orderService.processOrder(normalOrder);
        System.out.println("普通订单处理结果:" + normalResult);

        // 3. 测试2:处理秒杀订单
        System.out.println("\n======= 测试2:处理秒杀订单 =======");
        OrderDTO seckillOrder = new OrderDTO();
        seckillOrder.setOrderId("O20240317002");
        seckillOrder.setProductId("P2001");
        seckillOrder.setQuantity(1); // 秒杀限购1件
        seckillOrder.setAmount(99.9);
        seckillOrder.setOrderType("SECKILL");
        OrderResult seckillResult = orderService.processOrder(seckillOrder);
        System.out.println("秒杀订单处理结果:" + seckillResult);

        // 4. 测试3:处理非法秒杀订单(数量>1)
        System.out.println("\n======= 测试3:处理非法秒杀订单 =======");
        OrderDTO invalidSeckillOrder = new OrderDTO();
        invalidSeckillOrder.setOrderId("O20240317003");
        invalidSeckillOrder.setProductId("P2001");
        invalidSeckillOrder.setQuantity(2); // 秒杀限购1件,非法
        invalidSeckillOrder.setAmount(199.8);
        invalidSeckillOrder.setOrderType("SECKILL");
        OrderResult invalidResult = orderService.processOrder(invalidSeckillOrder);
        System.out.println("非法秒杀订单处理结果:" + invalidResult);

        context.close();
    }
}
输出结果
复制代码
======= 测试1:处理普通订单 =======
【订单服务】开始处理订单 | 订单ID:O20240317001,类型:NORMAL
【固定步骤】校验订单参数 | 订单ID:O20240317001
【固定步骤】参数校验通过 | 订单ID:O20240317001
【固定步骤】订单处理开始 | 订单ID:O20240317001,类型:NORMAL
【可变步骤-普通订单】扣减普通库存 | 订单ID:O20240317001,商品ID:P1001,数量:2
【固定步骤】生成支付单 | 订单ID:O20240317001,金额:199.8元
【可变步骤-普通订单】发送短信通知 | 订单ID:O20240317001
【固定步骤】订单处理完成 | 订单ID:O20240317001
普通订单处理结果:OrderResult(success=true, message=订单处理成功, order=OrderDTO(orderId=O20240317001, productId=P1001, quantity=2, amount=199.8, orderType=NORMAL))

======= 测试2:处理秒杀订单 =======
【订单服务】开始处理订单 | 订单ID:O20240317002,类型:SECKILL
【固定步骤】校验订单参数 | 订单ID:O20240317002
【固定步骤】参数校验通过 | 订单ID:O20240317002
【固定步骤】订单处理开始 | 订单ID:O20240317002,类型:SECKILL
【可变步骤-秒杀订单】扣减秒杀库存(加分布式锁) | 订单ID:O20240317002,商品ID:P2001,数量:1
【固定步骤】生成支付单 | 订单ID:O20240317002,金额:99.9元
【可变步骤-秒杀订单】发送APP推送通知 | 订单ID:O20240317002
【固定步骤】订单处理完成 | 订单ID:O20240317002
秒杀订单处理结果:OrderResult(success=true, message=订单处理成功, order=OrderDTO(orderId=O20240317002, productId=P2001, quantity=1, amount=99.9, orderType=SECKILL))

======= 测试3:处理非法秒杀订单 =======
【订单服务】开始处理订单 | 订单ID:O20240317003,类型:SECKILL
【固定步骤】校验订单参数 | 订单ID:O20240317003
【固定步骤】参数校验通过 | 订单ID:O20240317003
【固定步骤】订单处理开始 | 订单ID:O20240317003,类型:SECKILL
【可变步骤-秒杀订单】扣减秒杀库存(加分布式锁) | 订单ID:O20240317003,商品ID:P2001,数量:2
订单处理失败 | 订单ID:O20240317003,原因:秒杀订单限购1件
非法秒杀订单处理结果:OrderResult(success=false, message=订单处理失败:秒杀订单限购1件, order=OrderDTO(orderId=O20240317003, productId=P2001, quantity=2, amount=199.8, orderType=SECKILL))

四、模板方法模式的核心特点与适用场景

优点
  1. 复用固定流程代码:将子类共有的固定流程提取到父类,避免代码重复(如日志记录、参数校验);
  2. 标准化业务流程:强制子类遵循统一的流程骨架,防止流程混乱(如必须先校验再执行业务);
  3. 扩展灵活:子类只需实现可变步骤,无需修改父类核心流程,符合开闭原则;
  4. 控制流程执行 :通过final修饰模板方法,防止子类篡改流程;通过钩子方法控制可选步骤;
  5. 职责清晰:父类负责流程骨架,子类负责具体实现,符合单一职责原则;
  6. 便于维护:流程变更只需修改父类,所有子类自动生效。
缺点
  1. 类数量增加:每个可变流程对应一个子类,流程较多时类数量膨胀;
  2. 流程僵化:父类固定流程难以灵活调整,如需修改流程顺序,需修改父类(违反开闭原则);
  3. 学习成本:新手需理解父类流程和子类实现的关系,增加认知成本;
  4. 继承的局限性:模板方法基于继承实现,子类只能继承一个父类,限制了多流程复用。
适用场景
  1. 有固定流程的业务:如订单处理、支付流程、审批流程、数据导入导出;
  2. 需要复用固定代码的场景:如日志记录、参数校验、权限控制等通用步骤;
  3. 需要标准化流程的场景:如接口调用(请求→校验→调用→响应→日志);
  4. 框架扩展 :如 Spring 的JdbcTemplateRestTemplate(定义模板方法,用户实现回调);
  5. 算法骨架固定的场景:如排序算法(固定比较 - 交换流程,可变比较规则)、加密算法(固定加解密流程,可变密钥生成)。

五、JDK/ Spring 中的原生应用(必须知道)

模板方法模式是框架设计的核心模式之一,以下是常见的原生实现:

1. JDK 核心模板方法
  • java.io.InputStream/OutputStream :read()/write()是抽象方法(可变步骤),父类定义了流操作的固定流程;
  • java.util.AbstractList/AbstractSet/AbstractMap :父类定义集合操作的固定流程(如add()/remove()的校验逻辑),子类实现具体存储逻辑;
  • java.lang.Thread :run()是抽象方法(可变步骤),start()是模板方法(固定启动流程);
  • java.util.Calendar :父类定义日期计算的固定流程,子类(GregorianCalendar)实现具体日期算法。
2. Spring 核心模板方法
  • org.springframework.jdbc.core.JdbcTemplate :定义 JDBC 操作的固定流程(连接→执行→关闭→处理结果),用户通过RowMapper实现结果映射(可变步骤);
  • org.springframework.web.client.RestTemplate :定义 HTTP 请求的固定流程(请求构建→发送→响应处理),用户实现请求参数 / 响应解析(可变步骤);
  • org.springframework.transaction.support.TransactionTemplate :定义事务操作的固定流程(开启→执行→提交 / 回滚),用户实现业务逻辑(可变步骤);
  • Spring MVC的HandlerAdapter :定义请求处理的固定流程(拦截→处理→视图解析),用户实现Controller(可变步骤)。
3. MyBatis 核心模板方法
  • org.apache.ibatis.executor.BaseExecutor :定义 SQL 执行的固定流程(缓存→参数处理→执行→结果映射),子类(SimpleExecutor/BatchExecutor)实现具体执行逻辑;
  • org.apache.ibatis.session.defaults.DefaultSqlSession :定义会话操作的固定流程,子类实现具体 CRUD 逻辑。

六、模板方法模式 vs 策略模式(易混淆点)

模板方法和策略模式都用于封装可变逻辑,但核心意图完全不同:

维度 模板方法模式 策略模式
核心目的 固定流程 + 可变步骤(流程统一,步骤定制) 可变算法 + 统一接口(算法替换,接口统一)
实现方式 继承(子类重写父类抽象方法) 组合(上下文持有策略对象)
流程控制 父类控制流程,子类无法修改 客户端控制策略选择,上下文不控制流程
复用维度 复用流程骨架 复用算法实现
扩展方式 新增子类实现可变步骤 新增策略类实现新算法
典型场景 订单处理、JDBC 操作、框架模板 支付方式、排序算法、校验规则

总结

  1. 模板方法模式的核心是定义算法 / 业务的固定流程骨架,将可变步骤延迟到子类实现 ,既保证流程统一,又支持步骤定制;
  2. 核心角色包括抽象模板(定义流程 + 固定步骤 + 抽象方法 + 钩子方法)、具体模板(实现可变步骤);
  3. 模板方法需用final修饰,防止子类篡改流程;钩子方法用于控制可选步骤的执行;
  4. 模板方法模式适用于有固定流程、需要复用通用代码、标准化业务流程的场景(如订单处理、框架模板);
  5. 模板方法基于继承实现,策略模式基于组合实现,二者互补:模板方法固定流程,策略模式替换算法。
相关推荐
牢七2 小时前
PHP Debug配置记录
开发语言·php
ZPC82102 小时前
PPO 在ROS2 中训练与推理
人工智能·算法·机器人
IronMurphy2 小时前
【算法二十五】105. 从前序与中序遍历序列构造二叉树 236. 二叉树的最近公共祖先
java·数据结构·算法
2401_853576502 小时前
C++中的组合模式变体
开发语言·c++·算法
snakeshe10102 小时前
从 MySQL 到 Elasticsearch:构建高性能新闻爬虫的数据存储与搜索体系
java
技术小白菜2 小时前
海康平台通过代理播放视频流
java·java ee
学习3人组2 小时前
Workerman实现 WSS 基于客户端 ID 的精准推送
android·java·开发语言
百结2142 小时前
Nginx性能优化与监控实战
java·nginx·性能优化
jason_renyu2 小时前
Maven 新手完全使用指南(完整版)
java·maven·maven新手指南·maven新手完全使用指南·maven新手使用教程·maven教程