模板方法模式:从咖啡和茶到Spring框架,掌握设计模式的精髓
《Head First 设计模式》是一本经典的设计模式入门书籍,它以轻松幽默的方式讲解了设计模式的核心思想。其中,模板方法模式是一个非常简单但非常实用的设计模式,它可以帮助我们定义算法的骨架,同时将具体实现延迟到子类中。今天,我们就通过书中的例子,结合代码,来深入理解模板方法模式,并探讨它在JDK和Spring框架中的应用。
什么是模板方法模式?
模板方法模式的定义是:
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
简单来说,模板方法模式就是将算法的通用部分放在父类中,而将可变的部分交给子类去实现。这样既能避免代码重复,又能保证算法的结构不被破坏。
从咖啡和茶的故事说起
在《Head First 设计模式》中,作者用一个咖啡和茶的例子来讲解模板方法模式。我们来看看咖啡和茶的冲泡过程:
咖啡的冲泡过程
- 把水煮沸
- 用沸水冲泡咖啡
- 把咖啡倒进杯子
- 加糖和牛奶
茶的冲泡过程
- 把水煮沸
- 用沸水浸泡茶叶
- 把茶倒进杯子
- 加柠檬
我们发现,咖啡和茶的冲泡过程非常相似,只有第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");
}
}
第二步:实现具体子类
接下来,我们分别实现 Coffee
和 Tea
类,它们继承自 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
模板方法模式的核心思想
通过上面的例子,我们可以总结出模板方法模式的核心思想:
- 定义算法的骨架:将算法的通用部分放在父类中,形成一个模板方法。
- 延迟实现:将算法的可变部分延迟到子类中实现。
- 避免代码重复:通过抽象类将通用逻辑提取出来,避免子类重复代码。
模板方法模式的优点
- 代码复用:将通用逻辑放在父类中,子类只需关注自己的实现。
- 扩展性好:新增子类时,只需实现可变部分,而不需要修改算法的结构。
- 符合开闭原则:对扩展开放,对修改关闭。
模板方法模式的应用场景
模板方法模式非常适合以下场景:
- 固定流程,可变细节:例如数据处理流程、工作流引擎等。
- 避免重复代码:当多个类有相似的逻辑时,可以将通用部分提取到父类中。
- 框架设计:框架通常定义流程,而将具体实现交给用户。
模板方法模式在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
提供了一个通用的列表操作框架,而具体的实现(如 ArrayList
、LinkedList
等)可以根据自己的需求进行扩展。
模板方法模式在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 设计模式》中的咖啡和茶例子,我们可以清晰地理解模板方法模式的核心思想,并掌握如何在实际开发中应用它。
如果你正在学习设计模式,不妨从模板方法模式开始,用一杯咖啡和茶的时间,掌握设计模式的精髓!
互动话题:你在实际开发中用过模板方法模式吗?欢迎在评论区分享你的经验和心得!