一、模板方法定义

模板方法模式属于行为型设计模式,它定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

二、模式结构

模板方法模式包含两个主要角色:

  1. 抽象类

定义了一个或多个抽象操作(基本方法),供子类实现。这些是算法的可变部分。

实现了一个模板方法(最终方法),它是一个具体方法,给出了一个顶级逻辑的骨架。它按固定顺序调用基本方法以及其他已有方法。

  1. 具体子类

实现抽象类中定义的抽象方法,以完成算法中与特定子类相关的步骤。

三、实现步骤与代码示例

第1步:定义抽象类(定义算法骨架)

/**
 * 抽象类
 */
abstract class AbstractClass {
    /**
     * 模板方法
     */
    public void templateMethod() {
        // 抽象方法1,如参数验证
        abstractMethod1();
        // 抽象方法2,如初始化
        abstractMethod2();
        // 具体方法,给了默认具体实现,可重写
        concreteMethod();
    }

    /**
     * 具体方法
     */
    public void concreteMethod() {
        System.out.println("抽象类中的具体方法被调用...");
    }

    /**
     * 抽象方法1
     */
    public abstract void abstractMethod1();

    /**
     * 抽象方法2
     */
    public abstract void abstractMethod2();
}

第2步:创建具体子类(实现可变部分)

/**
 * 具体子类A
 */
class ConcreteAClass extends AbstractClass {
    public void abstractMethod1() {
        System.out.println("A抽象方法1的实现被调用...");
    }

    public void abstractMethod2() {
        System.out.println("A抽象方法2的实现被调用...");
    }
}

/**
 * 具体子类B
 */
class ConcreteBClass extends AbstractClass {
    public void abstractMethod1() {
        System.out.println("B抽象方法1的实现被调用...");
    }

    public void abstractMethod2() {
        System.out.println("B抽象方法2的实现被调用...");
    }
}

第3步:客户端使用

/**
 * 模板方法模式测试类
 */
public class TemplateMethodTest {

    public static void main(String[] args) {
        AbstractClass tmA = new ConcreteAClass();
        tmA.templateMethod();
      
        AbstractClass tmB = new ConcreteBClass();
        tmB.templateMethod();
      
    }
}

四、模式优点

  1. 封装不变部分,扩展可变部分:将认为不变的核心算法封装到父类,把可变的细节交给子类实现,便于子类扩展。

  2. 行为由父类控制,子类实现:父类的模板方法调用了其他抽象/具体/钩子方法,控制了整个流程,子类只需关心自己需要实现的细节。

  3. 代码复用:将公共的代码移到父类,避免了代码重复。

  4. 符合“开闭原则”:增加新的行为子类很容易,只需实现抽象方法即可,无需修改父类代码。


五、模式缺点

  1. 子类对父类产生了影响:子类的实现会影响父类模板方法的执行结果,这导致一种反向的控制结构,提高了代码阅读和理解的难度。

  2. 每增加一个不同的实现都需要创建一个子类,可能会导致类的个数增加,系统变得更加庞大。


六、应用场景

  1. 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。

  2. 各子类中公共的行为应被提取出来并集中到一个公共父类中,以避免代码重复。

  3. 需要控制子类的扩展。模板方法只在特定点调用“钩子”操作,这样就只允许在这些点进行扩展(比如 Spring 框架中的 JdbcTemplate)。


七、在 JDK 和 Spring 中的应用

  • Java CollectionsAbstractList, AbstractSet, AbstractMap 等类都使用了模板方法模式。例如,AbstractList 提供了 List 接口的骨架实现,get(int index) 是抽象方法,而 add(E e), indexOf(Object o) 等方法则基于 get 方法提供了默认实现。

  • Java IOInputStream, OutputStream, Reader, Writer 中的非抽象方法。

  • JUC中的AQS,提供了一套同步控制器的模板,通过这一套模板实现了ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等功能;

  • Tomcat中生命周期设计,LifecycleBase实现了init、start、stop、destroy等生命周期的方法,也是模板方法,也留了一些抽象方法 initInternal、startInternal、stopInternal、destoryInternal给子类去实现;

  • Spring FrameworkJdbcTemplate 是其最经典的应用。JdbcTemplate 定义了执行 SQL 的流程(获取连接、创建语句、执行、处理结果集、关闭资源等),而将“如何处理结果集”这个可变部分通过 RowMapperResultSetExtractor 接口留给用户来自定义。其他如RestTemplate, RedisTemplate, RabbitTemplate等都使用了模板方法模式;

Spring JdbcTemplate 简化示例

// Spring的模板方法控制了所有固定流程:连接、语句、执行、异常处理、资源清理
jdbcTemplate.query("SELECT * FROM users", new RowMapper<User>() {
    // 你只需要实现这个“可变部分”:如何将一行结果映射成一个对象
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setName(rs.getString("name"));
        return user;
    }
});

模板方法模式重要的2点:代码复用功能扩展,如果不想让子类实现,可以提供默认空实现或者抛异常。