简介
定义:模板方法设计模式是一种行为型设计模式,构建一个算法框架的方法,允许子类在维持算法整体结构不变的情况下,对算法的特定环节进行自定义实现。
是不是很蒙?当然定义是要结合实践去理解的。
使用场景:在重构的时候,发现写了两个或者多个几乎一样的类,只是有些方法的实现方式不一致,但是在使用这些类的地方调用方法顺序都一样,那就可以选择模板方法了!
UML类图:
如上图所示,模板方法模式关键点如下:一个模板类`CoffeeMaker`,里面有一个定义为`final`的模板方法`makeCoffee()`。 多个模板实现类。
实例场景:
想象一下,你是一家咖啡连锁店的老板,你们有一套标准的咖啡制作流程:选择咖啡豆 -> 研磨咖啡豆 -> 冲泡咖啡 -> 添加调料 -> 拉花(钩子方法,可选)。其中冲泡咖啡是通用的逻辑,是每个咖啡制作的必然流程。而选择咖啡豆、研磨咖啡豆、添加调料每种咖啡制作流程是不一样的,最后有的咖啡需要拉花。
代码实现
第一步:定义模板类以及模板方法
先定义一个模板类`CoffeeMaker`,如下代码片段所示,其中的`makeCoffee()`就是所谓的模板方法,为了不被子类重写,它被设置为`final`的,其定义了一个算法骨架。
其中的`brewCoffee()`是一个实体方法,里面是通用逻辑,所有的子类都是一样的,所有咖啡都需要冲泡。三个被`abstract`修饰的是抽象方法,这些方法是需要子类去根据自己的实际算法实现的。
而`latteArt()`方法有一个默认的空实现,这个一般称为==钩子方法==,设计用来被其中部分需要的子类重写。例如卡布奇诺咖啡需要拉花,美式不需要拉花。那么卡布奇诺类重写`latteArt`方法即可
// 抽象类,定义了咖啡制作的模板方法
public abstract class CoffeeMaker {
// 模板方法:定义了咖啡制作的固定步骤
public final void makeCoffee() {
chooseCoffeeBean();
grindCoffeeBean();
brewCoffee();
addSeasoning();
latteArt();
}
// 选择咖啡豆
public abstract void chooseCoffeeBean();
// 研磨咖啡豆
public abstract void grindCoffeeBean();
// 冲泡咖啡(默认实现,通用逻辑)
private void brewCoffee() {
System.out.println("冲泡咖啡");
}
// 添加调料,抽象方法,由子类实现
public abstract void addSeasoning();
/*钩子方法,可以被需要的子类overwrite*/
//是否拉花
public void latteArt() {
}
}
第二步:定义具体的实体类,根据情况重写相应的抽象方法和钩子方法。
值得注意的是,由于卡布奇诺咖啡需要拉花,所以它重写了`latteArt()`这个钩子方法。美式咖啡不需要拉花,所以不用重写`latteArt()`这个方法,只需覆盖其他抽象方法即可。
// 子类,实现具体步骤
public class Cappuccino extends CoffeeMaker{
@Override
public void chooseCoffeeBean() {
System.out.println("选择卡布奇诺咖啡豆");
}
@Override
public void grindCoffeeBean() {
System.out.println("研磨卡布奇诺咖啡豆");
}
@Override
public void addSeasoning() {
System.out.println("添加卡布奇诺调料");
}
@Override
public void latteArt() {
super.latteArt();
System.out.println("卡布奇诺拉花~");
}
}
public class Americano extends CoffeeMaker{
@Override
public void chooseCoffeeBean() {
System.out.println("选择美式咖啡豆");
}
@Override
public void grindCoffeeBean() {
System.out.println("研磨美式咖啡豆");
}
@Override
public void addSeasoning() {
System.out.println("添加美式调料");
}
}
第三步:客户端调用
// 客户端代码
public class CoffeeShop {
public static void main(String[] args) {
Cappuccino cappuccino = new Cappuccino();
cappuccino.makeCoffee();
System.out.println("----------------");
Americano americano = new Americano();
americano.makeCoffee();
}
}
结果显示:从输出可以清楚的看到,要做哪种咖啡就实例哪种咖啡的对象,但是大致流程都是一样的。
选择卡布奇诺咖啡豆
研磨卡布奇诺咖啡豆
冲泡咖啡
添加卡布奇诺调料
卡布奇诺拉花~
----------------
选择美式咖啡豆
研磨美式咖啡豆
冲泡咖啡
添加美式调料
总结
优点:
代码复用:模板方法模式让你能够复用代码,减少重复。
可扩展性:子类可以很容易地扩展或修改算法的特定步骤。
缺点:
类数量增加:每个不同的实现都需要一个子类,这可能会增加系统中类的总数。