模板方法(Template Method)作为Java的设计模式之一,一个词概括其优势特点那就是:抽象步骤
接下来以一个问答的形式进行理解记录
base代码:现在你要制作一个蛋糕,你会很多种做法,我们将制作蛋糕具象化成为代码,如下
public class Cake {
public static int METHOD_1 = 1;
public static int METHOD_2 = 2;
public void make(int method) {
System.out.println("make start");
if (method == METHOD_1) {
// 用方法1做蛋糕
} else if (method == METHOD_2) {
// 用方法2做蛋糕
}
System.out.println("make finish");
}
}
提问1:现在你灵光一闪,又想出一种制作方案你该怎么办?
回答1:你会毫不犹豫的说,追加一个method的static字段,以及追加一个if分支条件判断
public class Cake {
......
public static int METHOD_3 = 3;
public void make(int method) {
System.out.println("make start");
......
} else if (method == METHOD_3) {
// 用方法3做蛋糕
}
......
System.out.println("make finish");
}
}
提问2:这确实是一个解决方案,但是你有没有考虑过如果一个if里面有百余行,而且你又有非常多的制作内容怎么办,怎么解决冗长问题?
也许你会想,就加代码呗,反正我都熟悉。但是如果你不是重头写代码的人,你拿到这个类的时候就有N多种策略超过千行的代码,你是否会吐槽怎么全堆在一起了呢?如果你是一个优秀设计者或leader,review这套代码时能不想去优化吗?
为了更好的可扩展性和可读性我们可以引入模板方法:
首先我们应该抽出共通的东西做一个父类(Base类),其次具体的蛋糕制作由子类进一步实现,每一种制作方式我们就拓展一个子类
仔细观察Cake类,你会发现make方法是这个类的核心,make()中便是执行制作的核心代码,为了尽可能将共通的东西都留在父类(base类),我们要进一步将make()中的代码拆分,让其变成众多子步骤方法如下:
public abstract class Cake {
final void make() {
makeStart();//log
makingCakeGerm();//做蛋糕胚
makingCream();//制作奶油
wipeCakeGerm();//抹蛋糕胚
piping();//裱花
makeFinish();//log
}
private void makeStart() {
System.out.println("make start");
}
protected abstract String makingCakeGerm();
protected abstract String makingCream();
protected abstract String wipeCakeGerm();
protected abstract String piping();
private void makeFinish() {
System.out.println("make finish");
}
}
我们可以看到已经把make方法中的步骤进一步拆分细化,有先做蛋糕胚,再做奶油,然后涂抹到蛋糕胚上,最后裱花
我们来注意几个细节:
1、cake类成为了抽象类,没啥好说的,方法都抽象了
2、make方法增加了final修饰,不希望子类覆写这个方法,防止流程被更改
3、步骤抽象化,这是模板方法的核心,步骤的具体实现交由子类自行实现,每一种蛋糕都可以用不同的子类独立实现,这样可读性就大大提高了,而且扩展也非常方便,只需要实现一个新的子类即可
接下来我们实现具体的蛋糕子类:
我们这里可以这样想,蛋糕胚基本上没什么差别,抹奶油也都是常规操作,不一样的是制作什么样的奶油以及裱花的样式
public abstract class Cake {
final void make() {
makeStart();//log
makingCakeGerm();//做蛋糕胚
makingCream();//制作奶油
wipeCakeGerm();//抹蛋糕胚
piping();//裱花
makeFinish();//log
}
private void makeStart() {
System.out.println("make start");
}
private void makingCakeGerm() {
System.out.println("制作蛋糕胚");
}
protected abstract void makingCream();//内容不共通的方法,留在子类实现
private void wipeCakeGerm() {
System.out.println("将奶油抹到蛋糕胚上");
}
protected abstract void piping();//内容不共通的方法,留在子类实现
private void makeFinish() {
System.out.println("make finish");
}
}
子类实现蛋糕A和蛋糕B:
public class Cake_A extends Cake {
@Override
public void makingCream() {
System.out.println("植物奶油 抹茶味");
}
@Override
public void piping() {
System.out.println("裱花八朵");
}
}
/******************************************/
public class Cake_B extends Cake {
@Override
public void makingCream() {
System.out.println("动物奶油 巧克力味");
}
@Override
public void piping() {
System.out.println("裱个生日快乐");
}
}
最后Main类执行:
public class Test{
public static void main(String[] args){
Cake_A a = new Cake_A();
Cake_B b = new Cake_B();
a.make();
System.out.println("******************");
b.make();
}
}
看一下输出结果:
I/System.out: make start
I/System.out: 制作蛋糕胚
I/System.out: 植物奶油 抹茶味
I/System.out: 将奶油抹到蛋糕胚上
I/System.out: 裱花八朵
I/System.out: make finish
I/System.out: ******************
I/System.out: make start
I/System.out: 制作蛋糕胚
I/System.out: 动物奶油 巧克力味
I/System.out: 将奶油抹到蛋糕胚上
I/System.out: 裱个生日快乐
I/System.out: make finish
上述示例就是模板方法的一个实际使用模型。
总结一下优缺点:
优点:
- 复用性增强,可读性增强,将相同部分的代码放在抽象的父类中,部分实现代码在子类中完成
- 扩展性增强,通过增加子类来设计新的逻辑,一子类对应一套逻辑
- 遵守了开闭原则,并通过父类调用其子类的扩展方法实现了反向控制
缺点:
缺点很显然了,你如果拥有海量的蛋糕设计方法,那你就要实现非常多个子类
应用场景:
在逻辑设计时,将不变(也称共通)的逻辑做在父类中,变化的逻辑以抽象方法的形式在父类中,由子类后续继承实现,且后续的改动和拓展均应由子类完成,维持父类不动
-------------------------------解答时间----------------------------------
问:有人会说Test类应该是父类型引用指向子类对象,然后调用父类型变量中的.make()方法吧?
答:显然没必要,这种设计模式的核心思想不在多态上,向上转型诚然也可以实现,但是模板方法的核心在于抽象步骤,公共复用,所以用不用向上转型影响不大
问:有人学习了策略模式后会感到疑惑,这和模板方法也太像了吧?
答:不要怀疑,确实很像!区别就是策略模式用接口实现逻辑的扩展性,而模板方法用继承的方式完成逻辑的扩展性
---------------------------------------------------------------------------
虽然到这里就是模板方法的大体设计思想了,但是这还不算完,模板方法还有一个punchline!
“钩子方法”
提问3:现在你突然着急吃蛋糕了,那怎么办?砍步骤呗,最后那个裱花不要了,这个时候代码怎么写?
父类增加判断默认执行裱花,子类增加标记位isPipe,并提供设定标记位的函数isPipe(),这个函数就是钩子方法
public abstract class Cake {
final void make() {
makeStart();//log
makingCakeGerm();//做蛋糕胚
makingCream();//制作奶油
wipeCakeGerm();//抹蛋糕胚
if (isPipe()) {
piping();//裱花
}
makeFinish();//log
}
......
//父类增加一个方法用来判定标记位置,这里默认用true,后续子类拓展
protected boolean isPipe() {
return true;
}
}
public class Cake_C extends Cake {
private boolean isPipe = true;//是否裱花的标记位
@Override
public void makingCream() {
System.out.println("动物奶油 巧克力味");
}
@Override
public void piping() {
System.out.println("裱个生日快乐");
}
@Override
public boolean isPipe() {
return this.isPipe;
}
public void setPipe(boolean isPipe) {//设定标记位
this.isPipe = isPipe;
}
}
最后我们更改一下Test类的写法:
public class Test{
public static void main(String[] args){
Cake_A a = new Cake_A();
Cake_C c = new Cake_C();
c.setPipe(false);
a.make();
System.out.println("******************");
c.make();
}
}
我们在Test类中设定蛋糕C不裱花执行,这回蛋糕C没有裱花就做完了。
细想,父类其实并没有变,依然能够完整执行,但是却能靠子类的方法返回值,来改变最终的执行结果,这便是模板方法的点睛之笔了,而这篇日记到此也就完整了。