设计模式之十二:模板方法模式Spring应用与Java示例详解

一、模式概述

1.1 定义

**模板方法模式(Template Method Pattern)**​ 是一种行为型设计模式,它在一个抽象类中定义了一个算法的骨架,将某些步骤的具体实现延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些特定步骤。

1.2 核心思想

**"好莱坞原则"**​ - "不要打电话给我们,我们会打电话给你"(Don't call us, we'll call you)。父类控制整体流程,子类负责具体实现。

封装不变部分,扩展可变部分:将公共代码提取到父类,通过子类扩展新的实现。

1.3 结构组成

  • 抽象类(AbstractClass):定义算法骨架和模板方法

  • 具体类(ConcreteClass):实现抽象类中的抽象操作

1.4 关键方法类型

  • 模板方法(Template Method):定义算法骨架,通常用final修饰防止子类重写

  • 基本方法(Primitive Methods)

    • 抽象方法:由子类实现

    • 具体方法:父类已实现,子类可选择重写

    • 钩子方法:父类提供默认实现,子类可选择重写以影响模板方法行为

二、模式结构

复制代码
// 1. 抽象模板类
public abstract class AbstractTemplate {
    
    // 模板方法:定义算法骨架(通常声明为final,防止子类重写)
    public final void templateMethod() {
        // 步骤1:固定操作
        step1();
        
        // 步骤2:钩子方法(可选步骤)
        if (hookMethod()) {
            step2();
        }
        
        // 步骤3:抽象方法(必须由子类实现)
        abstractStep();
        
        // 步骤4:固定操作
        step3();
    }
    
    // 具体方法:已实现的方法
    private void step1() {
        System.out.println("执行步骤1");
    }
    
    // 具体方法:已实现的方法
    protected void step2() {
        System.out.println("执行步骤2");
    }
    
    private void step3() {
        System.out.println("执行步骤3");
    }
    
    // 抽象方法:必须由子类实现
    protected abstract void abstractStep();
    
    // 钩子方法:提供默认实现,子类可选择重写
    protected boolean hookMethod() {
        return true;
    }
}

// 2. 具体实现类A
public class ConcreteClassA extends AbstractTemplate {
    
    @Override
    protected void abstractStep() {
        System.out.println("具体类A实现抽象步骤");
    }
    
    @Override
    protected boolean hookMethod() {
        return false; // 选择不执行步骤2
    }
}

// 3. 具体实现类B
public class ConcreteClassB extends AbstractTemplate {
    
    @Override
    protected void abstractStep() {
        System.out.println("具体类B实现抽象步骤");
    }
    
    @Override
    protected void step2() {
        System.out.println("具体类B重写步骤2");
    }
}

三、Java基础示例

3.1 制作饮料示例

复制代码
// 抽象类:饮料制作模板
public abstract class BeverageTemplate {
    
    // 模板方法(final防止子类重写算法骨架)
    public final void prepareBeverage() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }
    
    // 抽象方法:由子类实现
    protected abstract void brew();
    protected abstract void addCondiments();
    
    // 具体方法:公共步骤
    private void boilWater() {
        System.out.println("烧开水");
    }
    
    private void pourInCup() {
        System.out.println("倒入杯中");
    }
    
    // 钩子方法:子类可选择重写
    protected boolean customerWantsCondiments() {
        return true;
    }
}

// 具体子类:制作咖啡
public class Coffee extends BeverageTemplate {
    @Override
    protected void brew() {
        System.out.println("冲泡咖啡粉");
    }
    
    @Override
    protected void addCondiments() {
        System.out.println("加入糖和牛奶");
    }
}

// 具体子类:制作茶
public class Tea extends BeverageTemplate {
    @Override
    protected void brew() {
        System.out.println("冲泡茶叶");
    }
    
    @Override
    protected void addCondiments() {
        System.out.println("加入柠檬");
    }
    
    // 重写钩子方法:不加调料
    @Override
    protected boolean customerWantsCondiments() {
        return false;
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        BeverageTemplate coffee = new Coffee();
        coffee.prepareBeverage();
        // 输出:烧开水、冲泡咖啡粉、倒入杯中、加入糖和牛奶
        
        BeverageTemplate tea = new Tea();
        tea.prepareBeverage();
        // 输出:烧开水、冲泡茶叶、倒入杯中
    }
}

四、Spring框架中的应用

4.1 JdbcTemplate - 经典应用

复制代码
// Spring的JdbcTemplate是模板方法模式的典型应用
@Repository
public class UserRepository {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public User findById(Long id) {
        String sql = "SELECT * FROM users WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{id}, 
            (rs, rowNum) -> {
                User user = new User();
                user.setId(rs.getLong("id"));
                user.setName(rs.getString("name"));
                user.setEmail(rs.getString("email"));
                return user;
            });
    }
}

// 模拟JdbcTemplate的核心结构
public abstract class JdbcTemplate {
    
    public final <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
        Connection con = null;
        try {
            // 固定步骤1:获取连接
            con = obtainConnection();
            
            // 固定步骤2:应用连接配置
            configureConnection(con);
            
            // 可变步骤:由回调接口实现
            return action.doInConnection(con);
            
        } catch (SQLException ex) {
            // 固定步骤3:异常处理
            throw translateException("ConnectionCallback", ex);
        } finally {
            // 固定步骤4:释放资源
            releaseConnection(con);
        }
    }
    
    protected abstract Connection obtainConnection() throws SQLException;
    protected abstract void releaseConnection(Connection con);
    
    // 其他模板方法...
}

4.2 RestTemplate - HTTP请求模板

复制代码
// RestTemplate中的模板方法模式
@RestController
public class UserController {
    
    @Autowired
    private RestTemplate restTemplate;
    
    public User getUserById(Long id) {
        String url = "http://api.example.com/users/{id}";
        return restTemplate.getForObject(url, User.class, id);
    }
}

// RestTemplate核心结构
public class RestTemplate extends InterceptingHttpAccessor 
    implements RestOperations {
    
    @Override
    public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
            @Nullable HttpEntity<?> requestEntity, Class<T> responseType,
            Object... uriVariables) throws RestClientException {
        
        // 请求准备(固定步骤)
        RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = 
            responseEntityExtractor(responseType);
        
        // 执行请求(模板方法)
        return execute(url, method, requestCallback, responseExtractor, uriVariables);
    }
    
    // 模板方法定义
    @Override
    public <T> T execute(String url, HttpMethod method, 
            @Nullable RequestCallback requestCallback,
            @Nullable ResponseExtractor<T> responseExtractor,
            Object... uriVariables) throws RestClientException {
        
        URI expanded = getUriTemplateHandler().expand(url, uriVariables);
        
        // 执行请求的固定流程
        return doExecute(expanded, method, requestCallback, responseExtractor);
    }
    
    // 具体实现交给子类
    protected abstract <T> T doExecute(URI url, HttpMethod method,
            @Nullable RequestCallback requestCallback,
            @Nullable ResponseExtractor<T> responseExtractor) 
            throws RestClientException;
}

4.3 TransactionTemplate - 事务管理

复制代码
// Spring事务管理的模板方法模式
@Service
public class UserService {
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void updateUser(User user) {
        transactionTemplate.execute(status -> {
            // 业务逻辑
            userRepository.update(user);
            return null;
        });
    }
}

// TransactionTemplate核心结构
public class TransactionTemplate extends DefaultTransactionDefinition
        implements TransactionOperations, InitializingBean {
    
    @Override
    public <T> T execute(TransactionCallback<T> action) throws TransactionException {
        
        // 事务管理固定流程
        TransactionStatus status = this.transactionManager.getTransaction(this);
        T result;
        try {
            // 可变部分:业务逻辑
            result = action.doInTransaction(status);
        } catch (RuntimeException | Error ex) {
            // 回滚(固定步骤)
            rollbackOnException(status, ex);
            throw ex;
        } catch (Throwable ex) {
            // 回滚(固定步骤)
            rollbackOnException(status, ex);
            throw new UndeclaredThrowableException(ex, 
                "TransactionCallback threw undeclared checked exception");
        }
        
        // 提交事务(固定步骤)
        this.transactionManager.commit(status);
        return result;
    }
}

五、实际应用案例

5.1 支付流程模板

复制代码
// 支付流程的模板方法模式实现
public abstract class PaymentProcessor {
    
    // 模板方法:支付流程
    public final PaymentResult processPayment(PaymentRequest request) {
        // 1. 验证支付参数
        validateRequest(request);
        
        // 2. 检查账户状态
        checkAccountStatus(request);
        
        // 3. 执行支付(抽象方法)
        PaymentResponse response = executePayment(request);
        
        // 4. 记录日志
        logPayment(request, response);
        
        // 5. 发送通知
        sendNotification(request, response);
        
        return buildResult(response);
    }
    
    // 具体方法
    protected void validateRequest(PaymentRequest request) {
        if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("无效的支付金额");
        }
        // 其他验证逻辑...
    }
    
    protected void checkAccountStatus(PaymentRequest request) {
        // 检查账户状态逻辑
        System.out.println("检查账户状态...");
    }
    
    // 抽象方法:不同支付方式有不同的实现
    protected abstract PaymentResponse executePayment(PaymentRequest request);
    
    // 钩子方法:默认发送通知,子类可重写
    protected void sendNotification(PaymentRequest request, PaymentResponse response) {
        System.out.println("发送支付成功通知");
    }
    
    protected void logPayment(PaymentRequest request, PaymentResponse response) {
        System.out.println("记录支付日志: " + request + ", " + response);
    }
    
    protected PaymentResult buildResult(PaymentResponse response) {
        return new PaymentResult(response.isSuccess(), response.getMessage());
    }
}

// 支付宝支付实现
@Service
public class AlipayProcessor extends PaymentProcessor {
    
    @Override
    protected PaymentResponse executePayment(PaymentRequest request) {
        System.out.println("使用支付宝支付: " + request.getAmount());
        // 调用支付宝API
        return new PaymentResponse(true, "支付宝支付成功");
    }
    
    @Override
    protected void sendNotification(PaymentRequest request, PaymentResponse response) {
        // 支付宝特有的通知方式
        System.out.println("发送支付宝支付成功通知");
    }
}

// 微信支付实现
@Service
public class WechatPayProcessor extends PaymentProcessor {
    
    @Override
    protected PaymentResponse executePayment(PaymentRequest request) {
        System.out.println("使用微信支付: " + request.getAmount());
        // 调用微信支付API
        return new PaymentResponse(true, "微信支付成功");
    }
}

// 使用示例
@RestController
@RequestMapping("/payment")
public class PaymentController {
    
    @Autowired
    private Map<String, PaymentProcessor> paymentProcessors;
    
    @PostMapping("/{type}")
    public PaymentResult pay(@PathVariable String type, 
                            @RequestBody PaymentRequest request) {
        PaymentProcessor processor = paymentProcessors.get(type + "Processor");
        if (processor == null) {
            throw new IllegalArgumentException("不支持的支付方式");
        }
        return processor.processPayment(request);
    }
}

5.2 数据导出模板

复制代码
// 数据导出模板
public abstract class DataExporter<T> {
    
    public final void export(List<T> data, String filePath) throws IOException {
        // 1. 准备导出
        prepareExport();
        
        try (OutputStream outputStream = new FileOutputStream(filePath);
             Writer writer = new OutputStreamWriter(outputStream, "UTF-8")) {
            
            // 2. 写入文件头
            writeHeader(writer);
            
            // 3. 写入数据
            for (T item : data) {
                writeItem(writer, item);
            }
            
            // 4. 写入文件尾
            writeFooter(writer);
            
            // 5. 导出完成处理
            onExportComplete(filePath, data.size());
            
        } catch (Exception e) {
            // 6. 异常处理
            handleExportError(e);
            throw e;
        }
    }
    
    protected void prepareExport() {
        System.out.println("准备导出数据...");
    }
    
    protected abstract void writeHeader(Writer writer) throws IOException;
    
    protected abstract void writeItem(Writer writer, T item) throws IOException;
    
    protected abstract void writeFooter(Writer writer) throws IOException;
    
    protected void onExportComplete(String filePath, int recordCount) {
        System.out.println("导出完成,文件: " + filePath + ", 记录数: " + recordCount);
    }
    
    protected void handleExportError(Exception e) {
        System.err.println("导出失败: " + e.getMessage());
    }
}

// CSV导出实现
public class CsvExporter<T> extends DataExporter<T> {
    
    private final Function<T, String[]> converter;
    
    public CsvExporter(Function<T, String[]> converter) {
        this.converter = converter;
    }
    
    @Override
    protected void writeHeader(Writer writer) throws IOException {
        // CSV不需要特殊头部
    }
    
    @Override
    protected void writeItem(Writer writer, T item) throws IOException {
        String[] fields = converter.apply(item);
        String line = String.join(",", 
            Arrays.stream(fields)
                .map(this::escapeCsv)
                .toArray(String[]::new));
        writer.write(line + "\n");
    }
    
    @Override
    protected void writeFooter(Writer writer) throws IOException {
        // CSV不需要特殊尾部
    }
    
    private String escapeCsv(String field) {
        if (field.contains(",") || field.contains("\"") || field.contains("\n")) {
            return "\"" + field.replace("\"", "\"\"") + "\"";
        }
        return field;
    }
}

六、模式优缺点

6.1 优点

  • 代码复用:将不变的行为移到父类,避免代码重复

  • 扩展性好:通过子类扩展新的行为,符合开闭原则

  • 封装不变部分:将算法骨架固定,细节由子类实现

  • 便于维护:算法的修改只需在父类中进行

  • 反向控制:父类调用子类操作,实现"好莱坞原则"

6.2 缺点

  • 类的数量增加:每个实现都需要一个具体子类

  • 骨架固定:限制了子类的灵活性,只能改变某些步骤

  • 可能违反里氏替换原则:如果子类修改了模板方法的步骤顺序

6.3 适用场景

  1. 多个类有相同的方法,但具体实现不同

  2. 需要控制子类的扩展点

  3. 重要、复杂的算法,希望将核心逻辑与具体实现分离

  4. 重构时发现多个类有相似的流程

  5. 多个子类有公共行为,但某些步骤实现不同

  6. 框架设计:定义框架骨架,让用户实现具体业务逻辑

七、最佳实践

7.1 Spring中的最佳实践

复制代码
// 1. 使用Callback接口作为可变部分
@FunctionalInterface
public interface TaskCallback<T> {
    T doInTask();
}

// 2. 模板类
@Component
public class RetryableTemplate {
    
    public <T> T executeWithRetry(Supplier<T> task, int maxRetries) {
        int retryCount = 0;
        while (retryCount <= maxRetries) {
            try {
                return task.get();
            } catch (Exception e) {
                retryCount++;
                if (retryCount > maxRetries) {
                    throw e;
                }
                waitForRetry(retryCount);
            }
        }
        throw new IllegalStateException("不应该执行到这里");
    }
    
    private void waitForRetry(int retryCount) {
        try {
            Thread.sleep(1000L * retryCount);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 3. 使用示例
@Service
public class OrderService {
    
    @Autowired
    private RetryableTemplate retryableTemplate;
    
    @Autowired
    private OrderRepository orderRepository;
    
    public Order createOrder(Order order) {
        return retryableTemplate.executeWithRetry(
            () -> orderRepository.save(order),
            3
        );
    }
}

7.2 结合策略模式

复制代码
// 模板方法模式 + 策略模式
public abstract class ReportGenerator {
    
    private ExportStrategy exportStrategy;
    
    public final void generateAndExport() {
        // 1. 准备数据
        List<ReportData> data = prepareData();
        
        // 2. 生成报告
        String report = generateReport(data);
        
        // 3. 使用策略导出
        exportStrategy.export(report);
        
        // 4. 清理资源
        cleanup();
    }
    
    protected abstract List<ReportData> prepareData();
    protected abstract String generateReport(List<ReportData> data);
    protected abstract void cleanup();
    
    public void setExportStrategy(ExportStrategy exportStrategy) {
        this.exportStrategy = exportStrategy;
    }
}

// 策略接口
public interface ExportStrategy {
    void export(String content);
}

7.3 注意事项

  1. 避免过度设计:如果子类数量很少或变化不大,不要滥用模板方法模式

  2. 控制抽象层级:模板方法中的步骤不宜过多,否则维护困难

  3. 命名规范:模板方法通常以"do"、"execute"、"process"等动词开头

  4. 钩子方法使用:合理使用钩子方法,避免子类被迫实现不需要的方法

  5. 识别变化点:当算法的变化点过多时,可能需要考虑其他模式如策略模式

八、总结

模板方法模式是Spring框架中广泛应用的核心模式之一,它体现了"好莱坞原则"的思想。通过将算法骨架固定,将具体实现延迟到子类,实现了代码复用和扩展性的平衡。

在Spring中,JdbcTemplate、RestTemplate、TransactionTemplate等都是模板方法模式的经典实现。在实际开发中,合理使用模板方法模式可以有效减少重复代码,提高系统可维护性,特别是在处理具有固定流程但具体实现不同的业务场景时。

正确识别和应用模板方法模式,可以让代码结构更加清晰,维护更加容易。同时要注意避免过度设计,根据实际业务需求选择合适的模式组合,如结合策略模式等,以构建更加灵活、可扩展的系统架构。

公众号同步,欢迎关注!

相关推荐
萧瑟余晖4 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
维天说7 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json
Zane19948 小时前
并发 vs 并行:别再傻傻分不清了,一文讲透 Java 并发编程的第一课
java·后端
杨充8 小时前
11.DDD与战术建模
设计模式·开源·代码规范
杨充8 小时前
12.综合实战图片框架
设计模式·开源·代码规范
噢,我明白了9 小时前
java中Excel的导入和导出(EasyExcel)
java·开发语言·excel
吠品9 小时前
Zabbix Web界面误报Server未运行的排查与解决
java·服务器·数据库
啊湘9 小时前
天气查询API接口 按月Token鉴权 实时天气 物联网可用 文档齐全
java·后端·struts
Java内核笔记9 小时前
告别十亿美元的错误 : Spring Boot 4 空安全 (JSpecify) 实战
java·spring boot·后端