模板方法(Template Method)作为Java的设计模式之一,一个词概括其优势特点那就是:抽象步骤
接下来以一个问答的形式进行理解记录
提问1:现在你要制作一个蛋糕,你会很多种做法,我们将制作蛋糕具象化到代码中如下,此刻你突发奇想又想出一种制作方案你该怎么办?
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:你毫不犹豫的会说,追加一个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) {
// 方法2
}
......
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("making cake germ");
}
protected abstract void makingCream();//内容不共通的方法,留在子类实现
private void wipeCakeGerm() {
System.out.println("wipe cake germ");
}
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
总结一下优缺点:
优点:
- 复用性增强,可读性增强
将相同部分的代码放在抽象的父类中,部分实现代码在子类中完成 - 扩展性增强
通过增加子类来设计新的行为,一子类一行为 - 遵守了开闭原则,并通过父类调用其子类的扩展行为,实现了反向控制
缺点:
缺点很显然了,你如果拥有海量的蛋糕设计方法,那你就要实现非常多个子类,那系统实现的复杂度也会变高(但总比写一起强)
应用场景:
在逻辑设计时,将不变(也称共通)的逻辑做在父类中,变化的逻辑以抽象方法的形式在父类中,由子类后续继承实现,且后续的改动和拓展均应由子类完成,维持父类不动
----------------------------------------------------------
解答时间:
问:有人会说测试类应该是父类型引用指向子类对象,然后调用父类型变量中的.make()方法吧?
答:显然没必要,这种设计模式的核心思想不在多态上,向上转型诚然也可以实现,但是模板方法的核心在于抽象步骤,公共复用,所以用不用向上转型影响不大
问:有人学习了策略模式后会感到疑惑,这和模板方法也太像了吧?
答:不要怀疑确实很像!区别就是策略模式用接口实现算法的扩展性 模板方法模式用继承的方式实现方法的扩展性
到此模板方法就差不多分享完了,但是这就完了?
模板方法还有一个点睛之笔,未完待续......