【HeadFirst系列之HeadFirst设计模式】第9天之模板方法模式:从咖啡和茶到Spring框架,掌握设计模式的精髓

Scroll Down

模板方法模式:从咖啡和茶到Spring框架,掌握设计模式的精髓

《Head First 设计模式》是一本经典的设计模式入门书籍,它以轻松幽默的方式讲解了设计模式的核心思想。其中,模板方法模式是一个非常简单但非常实用的设计模式,它可以帮助我们定义算法的骨架,同时将具体实现延迟到子类中。今天,我们就通过书中的例子,结合代码,来深入理解模板方法模式,并探讨它在JDK和Spring框架中的应用。

9-设计模式之模版方法模式-前言

什么是模板方法模式?

模板方法模式的定义是:

在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

简单来说,模板方法模式就是将算法的通用部分放在父类中,而将可变的部分交给子类去实现。这样既能避免代码重复,又能保证算法的结构不被破坏。


从咖啡和茶的故事说起

在《Head First 设计模式》中,作者用一个咖啡和茶的例子来讲解模板方法模式。我们来看看咖啡和茶的冲泡过程:

咖啡的冲泡过程

  1. 把水煮沸
  2. 用沸水冲泡咖啡
  3. 把咖啡倒进杯子
  4. 加糖和牛奶

茶的冲泡过程

  1. 把水煮沸
  2. 用沸水浸泡茶叶
  3. 把茶倒进杯子
  4. 加柠檬

我们发现,咖啡和茶的冲泡过程非常相似,只有第2步和第4步有所不同。那么,我们是否可以将这个冲泡过程抽象出来,形成一个模板方法呢?


用模板方法模式实现咖啡和茶

第一步:定义抽象类

我们定义一个抽象类 CaffeineBeverage,它包含了一个模板方法 prepareRecipe(),这个方法定义了冲泡饮料的算法骨架。然后,我们将一些步骤延迟到子类中实现。

abstract class CaffeineBeverage {

    // 模板方法,定义了算法的骨架
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    // 具体步骤,子类必须实现
    abstract void brew();

    abstract void addCondiments();

    // 通用步骤,所有子类共享
    void boilWater() {
        System.out.println("Boiling water");
    }

    void pourInCup() {
        System.out.println("Pouring into cup");
    }
}

第二步:实现具体子类

接下来,我们分别实现 CoffeeTea 类,它们继承自 CaffeineBeverage,并实现各自的 brew()addCondiments() 方法。

class Coffee extends CaffeineBeverage {

    @Override
    void brew() {
        System.out.println("Dripping Coffee through filter");
    }

    @Override
    void addCondiments() {
        System.out.println("Adding Sugar and Milk");
    }
}

class Tea extends CaffeineBeverage {

    @Override
    void brew() {
        System.out.println("Steeping the tea");
    }

    @Override
    void addCondiments() {
        System.out.println("Adding Lemon");
    }
}

第三步:测试代码

现在,我们可以通过调用 prepareRecipe() 方法来冲泡咖啡或茶。

public class CaffeineBeverageTest {

    public static void main(String[] args) {
        CaffeineBeverage coffee = new Coffee();
        CaffeineBeverage tea = new Tea();

        System.out.println("Making coffee...");
        coffee.prepareRecipe();

        System.out.println("\nMaking tea...");
        tea.prepareRecipe();
    }
}

运行结果:

Making coffee...
Boiling water
Dripping Coffee through filter
Pouring into cup
Adding Sugar and Milk

Making tea...
Boiling water
Steeping the tea
Pouring into cup
Adding Lemon

模板方法模式的核心思想

通过上面的例子,我们可以总结出模板方法模式的核心思想:

  1. 定义算法的骨架:将算法的通用部分放在父类中,形成一个模板方法。
  2. 延迟实现:将算法的可变部分延迟到子类中实现。
  3. 避免代码重复:通过抽象类将通用逻辑提取出来,避免子类重复代码。

模板方法模式的优点

  1. 代码复用:将通用逻辑放在父类中,子类只需关注自己的实现。
  2. 扩展性好:新增子类时,只需实现可变部分,而不需要修改算法的结构。
  3. 符合开闭原则:对扩展开放,对修改关闭。

模板方法模式的应用场景

模板方法模式非常适合以下场景:

  1. 固定流程,可变细节:例如数据处理流程、工作流引擎等。
  2. 避免重复代码:当多个类有相似的逻辑时,可以将通用部分提取到父类中。
  3. 框架设计:框架通常定义流程,而将具体实现交给用户。

模板方法模式在JDK中的应用

在JDK中,模板方法模式也有广泛的应用。例如,java.util.AbstractList 就是一个典型的模板方法模式实现。

AbstractList 是一个抽象类,它定义了列表操作的通用逻辑,而将具体的操作(如 get()set() 等)延迟到子类中实现。

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

    // 模板方法,定义了算法的骨架
    public boolean add(E e) {
        add(size(), e);
        return true;
    }

    // 具体步骤,子类必须实现
    abstract public E get(int index);

    abstract public E set(int index, E element);

    // 通用步骤,所有子类共享
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
}

通过这种方式,AbstractList 提供了一个通用的列表操作框架,而具体的实现(如 ArrayListLinkedList 等)可以根据自己的需求进行扩展。


模板方法模式在Spring框架中的应用

在Spring框架中,模板方法模式也有广泛的应用。例如,JdbcTemplate 就是一个典型的模板方法模式实现。

JdbcTemplate 提供了一个通用的数据库操作框架,而将具体的SQL执行逻辑交给用户实现。

public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {

    // 模板方法,定义了算法的骨架
    public <T> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException {
        return query(sql, new ResultSetExtractor<T>() {
            @Override
            public T extractData(ResultSet rs) throws SQLException, DataAccessException {
                return rse.extractData(rs);
            }
        });
    }

    // 具体步骤,用户必须实现
    public <T> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException {
        return execute(sql, new PreparedStatementCallback<T>() {
            @Override
            public T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
                ResultSet rs = null;
                try {
                    rs = ps.executeQuery();
                    return rse.extractData(rs);
                } finally {
                    JdbcUtils.closeResultSet(rs);
                }
            }
        });
    }
}

通过这种方式,JdbcTemplate 提供了一个通用的数据库操作框架,而用户只需关注自己的SQL执行逻辑。


总结

模板方法模式是设计模式中最简单但最实用的模式之一。它通过定义算法的骨架,将可变部分延迟到子类中,既保证了代码的复用性,又增强了系统的扩展性。

通过《Head First 设计模式》中的咖啡和茶例子,我们可以清晰地理解模板方法模式的核心思想,并掌握如何在实际开发中应用它。

如果你正在学习设计模式,不妨从模板方法模式开始,用一杯咖啡和茶的时间,掌握设计模式的精髓!


互动话题:你在实际开发中用过模板方法模式吗?欢迎在评论区分享你的经验和心得!