1、什么是模板方法模式
举例:生活的模板
去银行办理业务的通用过程,
Step1:进门取号
Step2:填写单据
Step3:等待叫号
Step4:窗口办理
这个过程不会因为是“你”或者是“我”有什么不同,银行为所有用户定制了这么一个模板,但是Step2中银行不知道每个客户的每笔业务,在这定制了这个步骤,留给每个客户具体实现。
接下来看一下模板方法模式是如何定义的?
首先模板方法模式为我们定义了一个操作中的算法骨架,骨架中定义了一些算法执行的步骤,但是框架中有一些步骤延迟到子类实现,如Step2。使得子类在不改变算法结构的同时,就重新定义该算法的某些特点步骤,这就是模板方法模式。
2、如何实现模板方法模式
以饮料的调制方法为例,
咖啡的泡法:
(1)把水煮沸
(2)用沸水冲泡咖啡
(3)把咖啡倒进杯子
(4)加糖和牛奶
茶的泡法:
(1)把水煮沸
(2)用沸水浸泡茶叶
(3)把茶倒进杯子
(4)加柠檬
观察这两种饮料的制备方法,发现有一些共性,比如把水煮沸,倒入杯子。而另外两个步骤体现出来不同,但往前看,不管是冲泡还是浸泡都是泡,不管加牛奶还是加柠檬都是加了一些调料。
基于这个认识上,认为提神饮料的泡法,可以分为一下步骤:
(1)把水煮沸(boilWater)
(2)泡饮料(brew)
(3)把饮料倒进杯子(pourInCup)
(4)加入调料(addCondiments)
代码实现:
基本实现:
(1)创建算法基类RefreshBeverage:
首先要先定一个共同遵循的模板方法。然后在模板方法中定义共有的方法。接下来在基本方法中,如boilWater()、pourInCup(),它们是对所有子类而言共同的方法,所以没有必要在对子类做过多的开放,可以生命为私有的方法,这样在子类编码的时候,减少我们的复杂度,这就是模板方法的好处。另外两个方法的共同特点是我们在算法框架中并不知道具体实现是什么样子的,所以应该做成抽象方法,并且由于需要在子类中可见,便于复写,所以声明为protected权限。
/**
* 抽象基类,为所有子类提供一个算法框架
*
*
* 提神饮料
*/
public abstract class RefreshBeverage {
/**
* 制备饮料的模板方法‘
* 封装了所有子类共同遵循的算法框架
*/
public final void prepareBeverageTemplate(){
//步骤1:将水煮沸
boilWater();
//步骤2:泡制饮料
brew();
//步骤3:将饮料倒入杯中
pourInCup();
//步骤4:加入调味料
addCondiments();
}
/**
* 抽象的基本方法:加入调料
*/
protected abstract void addCondiments();
/**
* 基本方法:将饮料倒入杯中
*/
private void pourInCup() {
System.out.println("将饮料倒入杯中");
}
/**
* 抽象的基本方法:泡制饮料
*/
protected abstract void brew();
/**
* 基本方法:将水煮沸
*/
private void boilWater(){
System.out.println("将水煮沸");
}
}
注意点:
① prepareBeverageTemplate一定要用final来修饰,因为模板方法模式使得抽象基类来定义了算法框架,而禁止子类对算法框架做任何的改变。所以使用final来阻止子类对模板方法的复写。
(2)创建子类Coffee:
重写所有基类中的抽象方法,实现延迟步骤。
/**
* 具体子类,提供了咖啡制备的具体实现
*/
public class Coffee extends RefreshBeverage {
@Override
protected void addCondiments() {
System.out.println("加入糖和牛奶");
}
@Override
protected void brew() {
System.out.println("用沸水冲泡咖啡");
}
}
(3)创建子类Tea:
/**
* 具体子类,提供了制备茶的具体实现
*/
public class Tea extends RefreshBeverage {
@Override
protected void addCondiments() {
System.out.println("加入柠檬");
}
@Override
protected void brew() {
System.out.println("用80度的热水浸泡茶叶5分钟");
}
}
(4)测试类的实现
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println("制备咖啡");
RefreshBeverage b1 = new Coffee();
b1.prepareBeverageTemplate();
System.out.println("咖啡好了");
System.out.println("/********************************************/");
System.out.println("制备茶");
RefreshBeverage b2 = new Tea();
b2.prepareBeverageTemplate();
System.out.println("茶好了");
}
}
改进实现:
比如有的朋友想喝一杯不加牛奶和糖的咖啡,或者有的朋友想喝一杯绿茶,这又该怎么实现呢?上面步骤4的实现是不是规定的太死,任何饮料都得加入调味品,怎么做一些个性化扩展呢?这里引入钩子方法概念。
当做一杯绿茶时的代码修改:
(1)首先将RefreshBeverage类修改如下:
在调用步骤4时加入判断,isCustomWantsCondiments()这个方法就是钩子方法,提供一个默认或空的实现,具体的子类可以自行决定是否挂钩以及如何挂钩。
/**
* 抽象基类,为所有子类提供一个算法框架
*
*
* 提神饮料
*/
public abstract class RefreshBeverage {
/**
* 制备饮料的模板方法‘
* 封装了所有子类共同遵循的算法框架
*/
public final void prepareBeverageTemplate(){
//步骤1:将水煮沸
boilWater();
//步骤2:泡制饮料
brew();
//步骤3:将饮料倒入杯中
pourInCup();
if (isCustomWantsCondiments()){
//步骤4:加入调味料
addCondiments();
}
}
/**
* Hook,钩子函数,提供一个默认或空的实现
* 具体的子类可以自行决定是否挂钩以及如何挂钩
* 询问用户是否加入调料
* @return
*/
protected boolean isCustomWantsCondiments() {
return true;
}
/**
* 抽象的基本方法:加入调料
*/
protected abstract void addCondiments();
/**
* 基本方法:将饮料倒入杯中
*/
private void pourInCup() {
System.out.println("将饮料倒入杯中");
}
/**
* 抽象的基本方法:泡制饮料
*/
protected abstract void brew();
/**
* 基本方法:将水煮沸
*/
private void boilWater(){
System.out.println("将水煮沸");
}
}
(2)然后修改子类Tea:
覆盖父类的钩子函数
/**
* 具体子类,提供了制备茶的具体实现
*/
public class Tea extends RefreshBeverage {
@Override
protected void addCondiments() {
System.out.println("加入柠檬");
}
@Override
protected void brew() {
System.out.println("用80度的热水浸泡茶叶5分钟");
}
/**
* 子类通过覆盖的形式选择挂载钩子函数
* @return
*/
@Override
protected boolean isCustomWantsCondiments() {
return false;
}
}
3、模板方法模式的特点
(1)模板方法模式实现要素:
① 抽象基类
包括基本方法(这种方法对不同实现子类而言是相同的,具有共性的)和抽象方法(只知道具体原则,不知道实现细节,需要延长到子类实现),还有钩子方法。然后将基本方法和抽象方法汇总而成一个模板方法(final修饰)
② 具体子类
实现基类中的抽象方法和覆盖钩子方法。
总结:准备一个抽象类,将部分逻辑以具体方法的形式实现,然后声明一些抽象方法交由子类实现剩余逻辑,用钩子方法给予子类更大的灵活性,最后将方法汇总构成一个不可改变的模板方法。
(2)适用场景:
① 算法或操作遵循相似的逻辑。
② 重构时(把相同的代码抽取到父类中)。
③ 重要、复杂的算法,核心算法设计为模板算法。
(3)优点:
① 封装性好
② 复用性好
③ 屏蔽细节
④ 便于维护
(4)缺点:
继承:单继承的缺点