模板方法模式算是设计模式里面比较好理解的一个设计模式,它的定义是:定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法模式的结构很简单,仅仅使用了Java的继承机制,应用十分广泛!
举例说明
同样的,虽然该模式很好理解,但是为了加深理解,我们还是用例子来举例,更容易记住这个模式!
我们以泡茶这个流程为例来说明。我们可以简单的定义一下泡茶的流程(并非标准流程):
- 首先将水煮沸
- 将我们要泡的茶的茶叶放入杯中
- 倒入我们的沸水,即可以饮用
这里我们总共有三个流程,不管是泡什么茶叶基本流程都是这样,区别就是在第二步放入的茶叶不同,而其他步骤是一样的。那我们完全可以将其他重复的步骤提取出来复用!下面我们用代码来实现一下可以更好的理解
首先是我们的泡茶抽象类(MakeTea)
package template;
public abstract class MakeTea {
//定义为final型,我们的子类是不可以修改泡茶的具体顺序的
public final void make(){
boilingWater();
addTeaLeaf();
pourBoilingWater();
}
// 首先是我们的将开水煮沸
public void boilingWater(){
System.out.println("首先将开水煮沸!");
}
// 加入我们的茶叶,定义为抽象类,由我们的子类来实现加入什么具体的茶叶
public abstract void addTeaLeaf();
// 倒入我们的沸水,开始饮用
public void pourBoilingWater(){
System.out.println("倒入我们的沸水,可以饮用了!");
}
}
然后我们是去泡一杯铁观音(TieGuanYin)和碧螺春(BiLouChun)试试!
package template;
//铁观音茶
public class TieGuanYin extends MakeTea {
@Override
public void addTeaLeaf() {
System.out.println("加入了铁观音茶叶!");
}
}
package template;
//碧螺春茶
public class BiLouChun extends MakeTea{
@Override
public void addTeaLeaf() {
System.out.println("加入碧螺春茶叶!");
}
}
我们在客户端测试一下
package template;
public class Client {
public static void main(String[] args) {
//初始化我们的两种茶
MakeTea tieGuanYin = new TieGuanYin();
MakeTea biLouChun = new BiLouChun();
//制作铁观音
tieGuanYin.make();
System.out.println("------------分割线------------");
//制作碧螺春
biLouChun.make();
}
}
控制台结果:
首先将开水煮沸!
加入了铁观音茶叶!
倒入我们的沸水,可以饮用了!
------------分割线------------
首先将开水煮沸!
加入碧螺春茶叶!
倒入我们的沸水,可以饮用了!
Process finished with exit code 0
可以看到我们基本都不用重新再去实现原来的步骤,只是在第二步换了我们添加的茶叶,极大提高了我们代码的复用性。但是呢,同时有一个问题,如果我们不想加入茶叶了呢,茶喝多了,想喝点开水。可以看到我们只需要第二步不加入茶叶就是开水了,所有就有了钩子来让用户是否实现抽象方法!
实现钩子
钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。这里我们就可以用钩子来实现用户是否选择要加入茶叶,如果不加入茶叶的,就是白开水了。
我们在我们的泡茶抽象类添加钩子方法
package template;
public abstract class MakeTea {
//定义为final型,我们的子类是不可以修改泡茶的具体顺序的
public final void make(){
boilingWater();
if(isAddTeaLeaf()){ //如果为true,我们就进行添加茶叶的操作
addTeaLeaf();
}
pourBoilingWater();
}
// 首先是我们的将开水煮沸
public void boilingWater(){
System.out.println("首先将开水煮沸!");
}
// 加入我们的茶叶,定义为抽象类,由我们的子类来实现加入什么具体的茶叶
public abstract void addTeaLeaf();
// 倒入我们的沸水,开始饮用
public void pourBoilingWater(){
System.out.println("倒入我们的沸水,可以饮用了!");
}
// 钩子方法,判断用户是否要加入茶叶,默认为添加
public boolean isAddTeaLeaf(){
return true;
}
}
然后去实现一杯白开水,然后重写钩子方法,改为false,就可以跳过我们的添加茶叶操作
package template;
public class BoiledWater extends MakeTea {
@Override
public void addTeaLeaf() {
//空实现,因为白开水不需要添加茶叶哦!
}
@Override
public boolean isAddTeaLeaf() {
return false; //返回false,表示我们不添加茶叶
}
}
然后在我们的客户端是实例化,并不影响我们之前的茶,而且也成功了泡了一杯白开水!
package template;
public class Client {
public static void main(String[] args) {
//初始化我们的两种茶
MakeTea tieGuanYin = new TieGuanYin();
MakeTea biLouChun = new BiLouChun();
//制作铁观音
tieGuanYin.make();
System.out.println("------------分割线------------");
//制作碧螺春
biLouChun.make();
System.out.println("------------分割线------------");
//制作一杯白开水
MakeTea boiledWater = new BoiledWater();
boiledWater.make();
}
}
控制台输出:
首先将开水煮沸!
加入了铁观音茶叶!
倒入我们的沸水,可以饮用了!
------------分割线------------
首先将开水煮沸!
加入碧螺春茶叶!
倒入我们的沸水,可以饮用了!
------------分割线------------
首先将开水煮沸!
倒入我们的沸水,可以饮用了!
Process finished with exit code 0
总结
模版方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
优点
- 封装不变部分,扩展可变部分
- 提取公共部分代码,便于维护
- 行为由父类控制,子类实现,符合开闭原则
缺点
颠倒了我们的设计习惯,一般是抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成具体的食物属性和方法;但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,这在复杂的项目
中,会带来代码阅读的难度,而且也会让新手产生不适感。
应用场景
- 多个子类共有的方法,并且逻辑基本相同
- 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个
子类实现。 - 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通
过钩子函数约束其行为。
(补充)
模板方法还是父类调用子类方法一个比较好的选择,而且在项目中也是符合我们的设计原则的。具体可以参考这一篇【Java面试】父类能调用子类的方法吗?
参考资料
设计模式之禅(第二版)