设计模式-模板模式


设计模式 - 模板模式

引言

在软件开发中,我们经常会遇到这样的场景:多个类有相似的业务流程,但在某些具体步骤上实现不同。如果每个类都重复编写相同的流程代码,不仅冗余,而且难以维护。这时,**模板模式(Template Pattern)**就派上用场了。

模板模式是行为型设计模式之一,它通过定义算法的骨架,将某些步骤的实现延迟到子类中,从而在不改变算法结构的前提下,让子类重新定义算法的某些特定步骤。

什么是模板模式

模板模式的核心思想非常简单:在一个方法中定义算法的骨架,将某些步骤推迟到子类中实现

更通俗地说,模板模式就像是一份”填空题”:父类已经把题目的框架和大部分内容都写好了,只留下几个空让子类来填。这样既保证了整体结构的一致性,又提供了足够的灵活性。

模式结构

模板模式主要包含两种角色:

  1. 抽象类(Abstract Class):定义算法的骨架,包含模板方法和基本方法

    • 模板方法:定义算法骨架,调用基本方法
    • 基本方法:抽象方法(由子类实现)或具体方法(子类可重写)
  2. 具体类(Concrete Class):实现抽象类中的抽象方法,完成特定步骤的具体实现

实战示例:游戏流程模板

让我们通过一个游戏流程的例子来深入理解模板模式。

场景描述

假设我们需要开发多种游戏,每种游戏的流程都包含三个步骤:初始化、开始游戏、结束游戏。虽然不同游戏的具体实现不同,但整体流程是一样的。

代码实现

1. 定义抽象模板类

public abstract class GameTemplate {
    
    /**
     * 模板方法:定义游戏流程的骨架
     * 使用final修饰,防止子类重写算法结构
     */
    public final void play() {
        initialize();
        startPlay();
        endPlay();
    }
    
    /**
     * 抽象方法:初始化游戏
     * 由子类具体实现
     */
    protected abstract void initialize();
    
    /**
     * 抽象方法:开始游戏
     * 由子类具体实现
     */
    protected abstract void startPlay();
    
    /**
     * 具体方法:结束游戏
     * 子类可以直接使用,也可以重写
     */
    protected void endPlay() {
        System.out.println("游戏结束,感谢游玩!");
    }
}

2. 实现具体游戏类

足球游戏:

public class Football extends GameTemplate {
    
    @Override
    protected void initialize() {
        System.out.println("足球游戏初始化:准备足球场地、球门、球员");
    }
    
    @Override
    protected void startPlay() {
        System.out.println("足球游戏开始:比赛进行中...");
    }
    
    @Override
    protected void endPlay() {
        System.out.println("足球比赛结束:全场比赛结束!");
    }
}

篮球游戏:

public class Basketball extends GameTemplate {
    
    @Override
    protected void initialize() {
        System.out.println("篮球游戏初始化:准备篮球场地、篮筐、球员");
    }
    
    @Override
    protected void startPlay() {
        System.out.println("篮球游戏开始:比赛进行中...");
    }
    
    // 使用父类的默认endPlay实现
}

3. 客户端调用

public class GameDemo {
    public static void main(String[] args) {
        System.out.println("=== 足球游戏 ===");
        GameTemplate football = new Football();
        football.play();
        
        System.out.println("\n=== 篮球游戏 ===");
        GameTemplate basketball = new Basketball();
        basketball.play();
    }
}

输出结果:

=== 足球游戏 ===
足球游戏初始化:准备足球场地、球门、球员
足球游戏开始:比赛进行中...
足球比赛结束:全场比赛结束!

=== 篮球游戏 ===
篮球游戏初始化:准备篮球场地、篮筐、球员
篮球游戏开始:比赛进行中...
游戏结束,感谢游玩!

代码解析

  1. 模板方法 play():使用 final 修饰,确保算法骨架不被修改
  2. 抽象方法initialize()startPlay() 强制子类实现,保证灵活性
  3. 具体方法endPlay() 提供默认实现,子类可选择重写或直接使用
  4. 访问控制:使用 protected 修饰,子类可以访问,外部不可直接调用

进阶示例:数据库操作模板

让我们看一个更实用的例子:数据库操作的模板模式实现。

public abstract class DatabaseTemplate {
    
    /**
     * 模板方法:执行数据库操作的完整流程
     */
    public final void execute() {
        Connection connection = null;
        try {
            // 1. 获取连接
            connection = getConnection();
            
            // 2. 执行具体操作
            doExecute(connection);
            
            // 3. 提交事务
            commit(connection);
            
        } catch (Exception e) {
            // 4. 异常处理
            rollback(connection);
            handleError(e);
        } finally {
            // 5. 关闭连接
            closeConnection(connection);
        }
    }
    
    // 基本方法
    protected abstract Connection getConnection() throws SQLException;
    protected abstract void doExecute(Connection connection) throws SQLException;
    
    // 具体方法(提供默认实现)
    protected void commit(Connection connection) throws SQLException {
        if (connection != null) {
            connection.commit();
        }
    }
    
    protected void rollback(Connection connection) {
        try {
            if (connection != null) {
                connection.rollback();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    protected void closeConnection(Connection connection) {
        try {
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    protected void handleError(Exception e) {
        System.err.println("数据库操作失败:" + e.getMessage());
        e.printStackTrace();
    }
}

使用示例:

public class UserDao extends DatabaseTemplate {
    
    @Override
    protected Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
    }
    
    @Override
    protected void doExecute(Connection connection) throws SQLException {
        String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
        PreparedStatement stmt = connection.prepareStatement(sql);
        stmt.setString(1, "张三");
        stmt.setString(2, "zhangsan@example.com");
        stmt.executeUpdate();
        System.out.println("用户数据插入成功");
    }
}

钩子方法

模板模式中还有一个重要概念:钩子方法(Hook Method)。钩子方法是抽象类中定义的具体方法,通常返回布尔值或空实现,子类可以重写这些方法来影响模板方法的执行流程。

示例:带钩子的游戏模板

public abstract class GameWithHook {
    
    public final void play() {
        initialize();
        
        // 钩子方法控制是否显示规则
        if (shouldShowRules()) {
            showRules();
        }
        
        startPlay();
        endPlay();
    }
    
    protected abstract void initialize();
    protected abstract void startPlay();
    protected void endPlay() {
        System.out.println("游戏结束!");
    }
    
    // 钩子方法:子类可重写
    protected boolean shouldShowRules() {
        return true;
    }
    
    // 具体方法
    private void showRules() {
        System.out.println("显示游戏规则...");
    }
}

// 使用钩子
public class ChessGame extends GameWithHook {
    @Override
    protected boolean shouldShowRules() {
        return false; // 象棋不显示规则
    }
    
    @Override
    protected void initialize() {
        System.out.println("初始化象棋游戏");
    }
    
    @Override
    protected void startPlay() {
        System.out.println("开始下棋");
    }
}

应用场景

模板模式在实际开发中应用非常广泛,以下是一些典型场景:

1. 框架和库的设计

  • Spring JdbcTemplate:封装了数据库操作的通用流程(获取连接、执行SQL、处理结果集、关闭连接)
  • HttpServletservice() 方法根据请求类型调用 doGet()doPost() 等方法

2. 业务流程处理

  • 订单处理流程:验证订单 → 扣减库存 → 计算价格 → 支付 → 发货
  • 审批流程:提交申请 → 各级审批 → 通知结果
  • 报表生成:查询数据 → 处理数据 → 生成报表 → 导出文件

3. 算法实现

  • 排序算法:不同排序算法有相同的骨架,但比较和交换的具体实现不同
  • 数据处理管道:读取 → 转换 → 过滤 → 输出

4. 测试框架

  • JUnit:测试的生命周期(setUp → test → tearDown)
  • 测试报告生成:收集结果 → 生成统计 → 输出报告

优点与缺点

优点

  1. 代码复用:将公共代码提取到父类,避免重复
  2. 反向控制:父类调用子类的方法,符合”好莱坞原则”(Don’t call us, we’ll call you)
  3. 扩展性好:新增功能只需新增子类,符合开闭原则
  4. 维护性强:修改算法骨架只需修改父类,不影响子类

缺点

  1. 类数量增加:每个实现都需要一个子类,可能导致类数量膨胀
  2. 继承限制:Java 单继承限制,子类无法继承其他类
  3. 理解难度:如果模板方法逻辑复杂,可能增加理解成本
  4. 调试困难:调用栈跨越多个类,调试时需要跳转

最佳实践

1. 模板方法使用 final

// 好的做法
public final void templateMethod() {
    step1();
    step2();
    step3();
}

// 不好的做法:允许子类修改算法结构
public void templateMethod() {
    step1();
    step2();
    step3();
}

2. 合理使用访问修饰符

public abstract class Template {
    // 模板方法:公开
    public final void templateMethod() { ... }
    
    // 基本方法:protected,子类可访问,外部不可调用
    protected abstract void step1();
    
    // 钩子方法:protected,子类可重写
    protected boolean shouldExecuteStep2() { return true; }
    
    // 辅助方法:private,仅本类使用
    private void helperMethod() { ... }
}

3. 合理设计抽象方法和具体方法

  • 抽象方法:必须由子类实现的核心逻辑
  • 具体方法:通用的、不易变化的逻辑
  • 钩子方法:提供扩展点,但不强制重写

4. 避免过度使用

不是所有相似逻辑都需要模板模式,如果:

  • 代码复用不多
  • 流程简单
  • 未来变化不大

那么直接继承或组合可能更简单。

模板模式 vs 其他模式

vs 策略模式

  • 模板模式:通过继承实现,算法骨架固定,部分步骤可变
  • 策略模式:通过组合实现,整个算法可替换

vs 建造者模式

  • 模板模式:关注算法流程控制
  • 建造者模式:关注对象的构建过程

vs 工厂方法模式

  • 模板模式:行为型模式,定义算法骨架
  • 工厂方法模式:创建型模式,定义对象创建接口

总结

模板模式是一种简单而强大的设计模式,它通过定义算法骨架并将具体实现延迟到子类,实现了代码复用和扩展性的平衡。在实际开发中,当我们发现多个类有相似的流程但某些步骤不同时,模板模式是一个很好的选择。

核心要点:

  1. 模板方法定义算法骨架,使用 final 修饰防止修改
  2. 抽象方法强制子类实现,保证灵活性
  3. 具体方法提供默认实现,子类可选择性重写
  4. 钩子方法提供扩展点,控制流程执行

记住: 设计模式不是银弹,要根据实际场景选择是否使用。模板模式适用于流程固定、部分步骤变化的场景,如果逻辑过于简单或变化太大,可能不需要使用模板模式。

参考资料

  • 《设计模式:可复用面向对象软件的基础》- GoF
  • 《Head First 设计模式》
  • Spring Framework Documentation
  • Effective Java (Third Edition)

作者:leejie
日期:2026年
标签:Java、设计模式、模板模式、架构设计


文章作者: Leejie
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Leejie !
评论
  目录