## 一、定义
>模板,顾名思义,它是一个固定化、标准化的东西。
模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。
## 二、场景问题
> 程序员不愿多扯,上来先干两行代码
网上模板方法的场景示例特别多,个人感觉还是《Head First 设计模式》中的例子比较好。
假设我们是一家饮品店的师傅,起码需要以下两个手艺
![](https://img.kancloud.cn/cd/66/cd66a58480f8217c6acf38b5874259f7_1080x348.png)
> 真简单哈,这么看,步骤大同小异,我的第一反应就是写个业务接口,不同的饮品实现其中的方法就行,像这样
![](https://img.kancloud.cn/e9/a8/e9a83d23615551551c9dd4057adf17b5_1080x526.png)
> 画完类图,猛地发现,第一步和第三步没什么差别,而且做饮品是个流程式的工作,我希望使用时,直接调用一个方法,就去执行对应的制作步骤。
灵机一动,不用接口了,用一个抽象父类,把步骤方法放在一个大的流程方法`makingDrinks()`中,且第一步和第三步,完全一样,没必要在子类实现,改进如下
![](https://img.kancloud.cn/54/72/54728310c6aa22c5c1e71a060f8a609f_1080x528.png)
> 再看下我们的设计,感觉还不错,现在用同一个`makingDrinks()`方法来处理咖啡和茶的制作,而且我们不希望子类覆盖这个方法,所以可以申明为 final,不同的制作步骤,我们希望子类来提供,必须在父类申明为抽象方法,而第一步和第三步我们不希望子类重写,所以我们声明为非抽象方法
```
publicabstractclass Drinks {
void boilWater() {
System.out.println("将水煮沸");
}
abstract void brew();
void pourInCup() {
System.out.println("倒入杯子");
}
abstract void addCondiments();
public final void makingDrinks() {
//热水
boilWater();
//冲泡
brew();
//倒进杯子
pourInCup();
//加料
addCondiments();
}
}
```
> 接着,我们分别处理咖啡和茶,这两个类只需要继承父类,重写其中抽象方法即可(实现各自的冲泡和添加调料)
```
publicclass Tea extends Drinks {
@Override
void brew() {
System.out.println("冲茶叶");
}
@Override
void addCondiments() {
System.out.println("加柠檬片");
}
}
```
```
publicclass Coffee extends Drinks {
@Override
void brew() {
System.out.println("冲咖啡粉");
}
@Override
void addCondiments() {
System.out.println("加奶加糖");
}
}
```
```
public static void main(String\[\] args) {
Drinks coffee = new Coffee();
coffee.makingDrinks();
System.out.println();
Drinks tea = new Tea();
tea.makingDrinks();
}
```
> 这就是模板方法模式,我们的`makingDrinks()`就是模板方法。我们可以看到相同的步骤`boilWater()`和`pourInCup()`只在父类中进行即可,不同的步骤放在子类实现。
## 三、认识模板方法
在阎宏博士的《JAVA与模式》一书中开头是这样描述模板方法(Template Method)模式的:
> 模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
写代码的一个很重要的思考点就是“变与不变”,程序中哪些功能是可变的,哪些功能是不变的,我们可以把不变的部分抽象出来,进行公共的实现,把变化的部分分离出来,用接口来封装隔离,或用抽象类约束子类行为。模板方法就很好的体现了这一点。
模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术,我们再看下类图
![](https://img.kancloud.cn/07/e0/07e05268125626056752aef964e0631b_1080x488.png)
模板方法模式就是用来创建一个算法的模板,这个模板就是方法,该方法将算法定义成一组步骤,其中的任意步骤都可能是抽象的,由子类负责实现。这样可以确保算法的结构保持不变,同时由子类提供部分实现。
再回顾下我们制作咖啡和茶的例子,有些顾客要不希望咖啡加糖或者不希望茶里加柠檬,我们要改造下模板方法,在加相应的调料之前,问下顾客
```
publicabstractclass Drinks {
void boilWater() {
System.out.println("将水煮沸");
}
abstract void brew();
void pourInCup() {
System.out.println("倒入杯子");
}
abstract void addCondiments();
public final void makingDrinks() {
boilWater();
brew();
pourInCup();
//如果顾客需要,才加料
if (customerLike()) {
addCondiments();
}
}
//定义一个空的缺省方法,只返回 true
boolean customerLike() {
returntrue;
}
}
```
如上,我们加了一个逻辑判断,逻辑判断的方法时一个只返回 true 的方法,这个方法我们叫做钩子方法。
> 钩子:在模板方法的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
钩子方法一般是空的或者有默认实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。而要不要挂钩,又由子类去决定。
是不是很有用呢,我们再看下咖啡的制作
```
publicclass Coffee extends Drinks {
@Override
void brew() {
System.out.println("冲咖啡粉");
}
@Override
void addCondiments() {
System.out.println("加奶加糖");
}
//覆盖了钩子,提供了自己的询问功能,让用户输入是否需要加料
boolean customerLike() {
String answer = getUserInput();
if (answer.toLowerCase().startsWith("y")) {
returntrue;
} else {
returnfalse;
}
}
//处理用户的输入
private String getUserInput() {
String answer = null;
System.out.println("您想要加奶加糖吗?输入 YES 或 NO");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
try {
answer = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
if (answer == null) {
return"no";
}
return answer;
}
}
```
接着再去测试下代码,看看结果吧。
![](https://img.kancloud.cn/48/60/48605d7c8b5cfd3a7a428da6dfc00bf6_1080x202.png)
我想你应该知道钩子的好处了吧,它可以作为条件控制,影响抽象类中的算法流程,当然也可以什么都不做。
模板方法有很多种实现,有时看起来可能不是我们所谓的“中规中矩”的设计。接下来我们看下 JDK 和 Spring 中是怎么使用模板方法的。
## 四、 JDK 中的模板方法
我们写代码经常会用到comparable比较器来对数组对象进行排序,我们都会实现它的`compareTo()`方法,之后就可以通过`Collections.sort()`或者`Arrays.sort()`方法进行排序了。
具体的实现类就不写了(可以去 github:starfish-learning 上看我的代码),看下使用
```
@Override
public int compareTo(Object o) {
Coffee coffee = (Coffee) o;
if(this.price < (coffee.price)){
return -1;
}elseif(this.price == coffee.price){
return0;
}else{
return1;
}
}
```
```
public static void main(String\[\] args) {
Coffee\[\] coffees = {new Coffee("星冰乐",38),
new Coffee("拿铁",32),
new Coffee("摩卡",35)};
Arrays.sort(coffees);
for (Coffee coffee1 : coffees) {
System.out.println(coffee1);
}
}
```
![](https://img.kancloud.cn/99/68/9968a38ea9491ecbe897e525e4260e7e_1080x151.png)
你可能会说,这个看着不像我们常规的模板方法,是的。我们看下比较器实现的步骤
1. 构建对象数组
2. 通过 Arrays.sort 方法对数组排序,传参为`Comparable`接口的实例
3. 比较时候会调用我们的实现类的`compareTo()`方法
4. 将排好序的数组设置进原数组中,排序完成
一脸懵逼,这个实现竟然也是模板方法。
这个模式的重点在于提供了一个固定算法框架,并让子类实现某些步骤,虽然使用继承是标准的实现方式,但通过回调来实现,也不能说这就不是模板方法。
其实并发编程中最常见,也是面试必问的 AQS 就是一个典型的模板方法。
## 五、Spring 中的模板方法
Spring 中的设计模式太多了,而且大部分扩展功能都可以看到模板方式模式的影子。
我们看下 IOC 容器初始化时中的模板方法,不管是 XML 还是注解的方式,对于核心容器启动流程都是一致的。
`AbstractApplicationContext`的`refresh`方法实现了 IOC 容器启动的主要逻辑。
一个`refresh()`方法包含了好多其他步骤方法,像不像我们说的模板方法,`getBeanFactory()`、`refreshBeanFactory()`是子类必须实现的抽象方法,`postProcessBeanFactory()`是钩子方法。
```
publicabstractclass AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
}
// 两个抽象方法
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
//钩子方法
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
}
```
打开你的 IDEA,我们会发现常用的`ClassPathXmlApplicationContext`和`AnnotationConfigApplicationContext`启动入口,都是它的实现类(子类的子类的子类的...)。
`AbstractApplicationContext`的一个子类`AbstractRefreshableWebApplicationContext`中有钩子方法`onRefresh()`的实现:
```
publicabstractclass AbstractRefreshableWebApplicationContext extends …… {
/\*\*
\* Initialize the theme capability.
\*/
@Override
protected void onRefresh() {
this.themeSource = UiApplicationContextUtils.initThemeSource(this);
}
}
```
![](https://img.kancloud.cn/31/df/31dfbcc2a55fae14c1d68d3af5481621_1080x674.png)
## 六、小总结
优点:1、封装不变的部分,扩展可变的部分。2、提取公共代码,便于维护。3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景:1、有多个子类共有的方法,且逻辑相同。2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
- 前言
- 第一章 设计七大原则
- 第1节 开闭原则
- 第2节 依赖倒置原则
- 第3节 单一职责原则
- 第4节 接口隔离原则
- 第5节 迪米特法则
- 第6节 里氏替换原则
- 第7节 合成复用原则
- 第二章 简单工厂模式
- 第1节 使用场景
- 第2节 示例代码
- 第三章 创建者模式
- 第1节 工厂方法模式
- 第2节 抽象工厂模式
- 第3节 建造者模式
- 第4节 原型模式
- 第5节 单例模式
- 第四章 结构型模式
- 第1节 适配器模式
- 第2节 桥接模式
- 第3节 组合模式
- 第4节 装饰者模式
- 第5节 外观模式
- 第6节 享元模式
- 第7节 代理模式
- 第五章 行为模式
- 第1节 责任链模式
- 第2节 命令模式
- 第3节 迭代器模式
- 第4节 中介者模式
- 第5节 备忘录模式
- 第6节 观察者模式
- 第7节 状态模式
- 第8节 策略模式
- 第9节 模板方法模式
- 第10节 访问者模式
- 第11节 解释器模式