设计模式之十二:模板方法模式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等都是模板方法模式的经典实现。在实际开发中,合理使用模板方法模式可以有效减少重复代码,提高系统可维护性,特别是在处理具有固定流程但具体实现不同的业务场景时。

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

公众号同步,欢迎关注!

相关推荐
灯火不休ᝰ2 小时前
[kotlin] 从Java到Kotlin:掌握基础语法差异的跃迁指南
java·kotlin·安卓
KoiHeng2 小时前
Java的文件知识与IO操作
java·开发语言
czlczl200209252 小时前
Spring Data Redis
java·redis·spring
知识即是力量ol2 小时前
在客户端直接上传文件到OSS
java·后端·客户端·阿里云oss·客户端直传
闻哥2 小时前
深入理解 Spring @Conditional 注解:原理与实战
java·jvm·后端·python·spring
煜磊3 小时前
MD5加盐值-注册与登录
java·开发语言
东东5163 小时前
校园求职招聘系统设计和实现 springboot +vue
java·vue.js·spring boot·求职招聘·毕设
Cult Of3 小时前
锁正确使用
java
long3163 小时前
K‘ 未排序数组中的最小/最大元素 |期望线性时间
java·算法·排序算法·springboot·sorting algorithm