模板方法模式(Templatepattern):在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤.
模板方法模式的实现要素:
准备一个抽象类,将部分逻辑以具体方法的形式实现,并不可改变。然后声明一些抽象方法交由子类实现剩余逻辑,用钩子方法给予子类更大的灵活性。最后将方法汇总构成一个不可改变的模板方法。
模板方法模式的结构
模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术。模板方法模式的静态结构如下图所示:
抽象基类(Abstract Template):
1、定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。
2、定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
具体子类(Concrete Template):
1、实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。
2、每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。
接下来源码:
抽象基类(AbstractTemplate),abstractMethod()、hookMethod()等基本方法是顶级逻辑的组成步骤,这个顶级逻辑由templateMethod()方法代表,并且顶级逻辑是不可改变的。
/**
* 抽象模板角色类,abstractMethod()、hookMethod()等基本方法是顶级逻辑的组成步骤,
* 这个顶级逻辑由templateMethod()方法代表。
*
* @author JUN
*
*/
public abstract class AbstractTemplate {
// 模板方法 (不可改动的)
public final void templateMethod() {
// 调用基本方法
abstractMethod();
hookMethod();
concreteMethod();
}
// 基本方法的声明(由子类实现)必须由子类置换
protected abstract void abstractMethod();
// 基本方法(空方法)可以由子类置换
protected void hookMethod() {
}
// 基本方法(已经实现)子类不可以动
private final void concreteMethod() {
// 业务相关的代码
}
}
具体子类(ConcreteTemplate),实现了父类所声明的基本方法,abstractMethod()方法所代表的就是强制子类实现的剩余逻辑,而hookMethod()方法是可选择实现的逻辑,不是必须实现的。
/**
* 具体模板角色类,实现了父类所声明的基本方法,
* abstractMethod()方法所代表的就是强制子类实现的剩余逻辑,
* 而hookMethod()方法是可选择实现的逻辑,不是必须实现的。
* @author JUN
*
*/
public class ConcreteTemplate extends AbstractTemplate {
// 基本方法的实现
@Override
public void abstractMethod() {
// 业务相关的代码
}
// 重写父类的方法
@Override
public void hookMethod() {
// 业务相关的代码
}
}
模板方法模式的关键是:子类可以置换掉父类的可变部分,但是子类却不可以改变模板方法所代表的顶级逻辑,以及基本实现。
每当定义一个新的子类时,不要按照控制流程的思路去想,而应当按照“责任”的思路去想。换言之,应当考虑哪些操作是必须置换掉的,哪些操作是可以置换掉的,以及哪些操作是不可以置换掉的。使用模板模式可以使这些责任变得清晰。
模板方法模式的实现要素:
抽象基类:
1、基本方法(final)
2、抽象方法
3、可选的钩子方法
4、模板方法(final)
具体子类:
1、实现基类中的抽象方法
2、根据需求选择去覆盖钩子方法
接下来我们用形象的例子实现一下:
源码:
抽象基类(RefreshBeverage):
/**
* 抽象基类,为所有子类提供一个算法框架(提神饮料)
*
* @author JUN
*
*/
public abstract class RefreshBeverage {
/**
* 制备饮料的模板方法
* 封装了所有子类共同遵循的算法框架
*/
public final void prepareBeverageTemplate() {
// 1、把水煮沸(boilWater)
boilWater();
// 2、泡饮料(brew)
brew();
// 3、把饮料倒进杯子(pourInCup)
pourInCup();
// 4、加调味料(addCondiments)
if(isWaters()){
addCondiments();
}
}
//询问用户是否加入调料,Hook方法(钩子函数)
protected boolean isWaters() {
return true;
}
//基本方法,把水煮沸
private final void boilWater() {
System.out.println("把水煮沸");
}
//基本方法,把饮料倒进杯子
private final void pourInCup() {
System.out.println("把饮料倒进杯子");
}
//抽象的基本方法,泡制饮料
protected abstract void brew();
//抽象的基本方法,加入调味料
protected abstract void addCondiments();
}
具体子类(Coffee):
/**
* 具体的子类,提供了咖啡制备的具体实现
* @author JUN
*
*/
public class Coffee extends RefreshBeverage {
@Override
protected void brew() {
System.out.println("用沸水冲泡咖啡");
}
@Override
protected void addCondiments() {
System.out.println("加糖和牛奶");
}
}
具体子类(Tea):
/**
* 具体的子类,提供了制备茶的具体实现
* @author JUN
*
*/
public class Tea extends RefreshBeverage {
@Override
protected void brew() {
System.out.println("用沸水浸泡茶叶");
}
@Override
protected void addCondiments() {
System.out.println("加柠檬");
}
//不加柠檬,我要一杯纯正的茶水
@Override
protected boolean isWaters(){
return false;
}
}
接下来我们新建一个客户端类来测试一下:
public class Client {
public static void main(String[] args) {
//冲咖啡
System.out.println("冲咖啡啦");
RefreshBeverage c = new Coffee();
c.prepareBeverageTemplate();
System.out.println("---------------");
//冲茶
System.out.println("冲茶啦");
RefreshBeverage t = new Tea();
t.prepareBeverageTemplate();
}
}
运行结果:
总结:
优点:
1、封装性好
2、复用性好
3、屏蔽细节
4、便于维护
缺点:
使用模板方法模式,使得我们的类已经处于继承状态不能在继承其他类了(Java单继承)
模板方法的使用场景:
1、算法或操作遵循相似的逻辑
2、重构时(把相同的代码抽取到父类中)
3、重要、复杂的算法,核心算法设计为模板算法