小明拿到了一个半成品
这天,主管发了一个项目给小明,说这是之前咖啡厅的一个小需求,他们的生意越来越好,开始买其他的饮品了,比如茶。咖啡店想把泡咖啡和泡茶的过程规范一下,所以需要小明他们公司迭代一个程序的版本。
但是小明打开项目后,发现项目里面只有两个类,其中一个是抽象类,而主管的说,你的任务就是实现这个抽象类,完成这个项目。
饮料抽象类:
public abstract clsaa Beverage{
final void made(){
boilWater();
addBeverage();
waterInCup();
addCondiments();
}
abstract void addBeverage();
abstract void addCondiments();
void boilWater(){
// 煮开水
}
void waterInCup(){
// 把水放进水杯
}
}
很快小明实现了咖啡和茶:
public clsaa Coffee extends Beverage{
void addBeverage(){
// 加咖啡
}
void addCondiments(){
// 加其他调料
}
}
public clsaa Tea extends Beverage{
void addBeverage(){
// 加茶叶
}
void addCondiments(){
// 加其他调料
}
}
主管说好,我们现在可以开始煮咖啡、泡茶了:
public class Customer {
public static void main(String[] args){
// 煮咖啡
Beverage coffee = new Beverage();
coffee.made();
// 泡茶
Beverage tea = new Tea();
tea.made();
}
}
小明发现这种方式真不错呢,于是请教了主管,这是什么用法。主管说,这叫模板方法模式,我们可以看到在抽象类中,我们定义了一些抽象方法,这些方法由子类去实现,还定义了一些默认实现的方法(boilWater()、waterInCup()),另外还有一个被final修饰的made()方法,我们把这个方法就称为模板方法
(不会被子类覆盖),它调用了一些抽象方法和已经实现的方法去实现,抽象方法中的实现不是在抽象类中,而是在子类中,这就是我们的模板方法模式。
定义
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤。
优点:
- 模板方法模式在一个类中形式化地定义算法,而由它的子类实现细节的处理
- 模板方法是一种代码复用的基本技术。它们在类库中尤为重要,它们提取了类库中的公共行为
- 模板方法模式导致一种反向的控制结构,这种结构有时被称为“好莱坞法则” ,即“别找我们,我们找你”通过一个父类调用其子类的操作(而不是相反的子类调用父类),通过对子类的扩展增加新的行为,符合“开闭原则”
缺点:
- 每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,但是更加符合“单一职责原则”,使得类的内聚性得以提高
再加一个方法
public abstract clsaa Beverage{
final void made(){
boilWater();
addBeverage();
waterInCup();
addCondiments();
addIce();
}
abstract void addBeverage();
abstract void addCondiments();
void boilWater(){
// 煮开水
}
void waterInCup(){
// 把水放进水杯
}
void addIce(){
// 不实现
}
}
在抽象类里面,我们又加了一个方法,偶尔顾客可能需要在咖啡或者茶里面加冰块,于是增加了addIce()方法。他和之前我们的三种方法都不同,他是一个没有实现的方法——钩子方法。那钩子方法的作用是什么呢,模板方法定义了整个算法,子类实现算法中的一部分,但是仅仅是这样,子类的作用就只是实现,而为了让子类也能做一些决定,于是加入了钩子方法,也就是钩子方法是为了让子类也可以做一些决定,如果它不想做决定,那么不去覆盖钩子方法就可以了。仿佛有点绕了,我们来理一理:抽象对象依赖了实现它的子类,子类的整个功能又依赖了它的抽象父类,父类由依赖了钩子方法,而钩子方法又由子类来决定,这不违背了我们的依赖倒置原则吗?其实它是遵循了我们的另外一个原则好莱坞原则
:别调用我们,我们会调用你。子类不需要调用父类,而通过父类来调用子类,子类可以实现父类的可变部份,却继承父类的逻辑,不能改变业务逻辑。
现实中的模板方法使用
Java中大家一定使用过Arrays.sort(Object[] array)方法,其实它就是模板方法的一个应用,不过它又和我们上面的实现有所不同,看看排序具体的方法:
private static void binarySort(Object[] a, int lo, int hi, int start) {
if (DEBUG) assert lo <= start && start <= hi;
if (start == lo)
start++;
for ( ; start < hi; start++) {
@SuppressWarnings("unchecked")
Comparable<Object> pivot = (Comparable) a[start];
// Set left (and right) to the index where a[start] (pivot) belongs
int left = lo;
int right = start;
if (DEBUG) assert left <= right;
/*
* Invariants:
* pivot >= all in [lo, left).
* pivot < all in [right, start).
*/
while (left < right) {
int mid = (left + right) >>> 1;
if (pivot.compareTo(a[mid]) < 0)
right = mid;
else
left = mid + 1;
}
if (DEBUG) assert left == right;
/*
* The invariants still hold: pivot >= all in [lo, left) and
* pivot < all in [left, start), so pivot belongs at left. Note
* that if there are elements equal to pivot, left points to the
* first slot after them -- that's why this sort is stable.
* Slide elements over to make room to make room for pivot.
*/
int n = start - left; // The number of elements to move
// Switch is just an optimization for arraycopy in default case
switch(n) {
case 2: a[left + 2] = a[left + 1];
case 1: a[left + 1] = a[left];
break;
default: System.arraycopy(a, left, a, left + 1, n);
}
a[left] = pivot;
}
}
注意两个地方:Comparable<Object> pivot = (Comparable) a[start]
和if (pivot.compareTo(a[mid]) < 0)
,首先它将我们做排序的类类型转换成了Comparable接口,而这个接口有一个方法需要实现就是:
int compareTo(T another);
大家应该比较清楚了,如果我们需要自定义我们对象的排序规则,就需要实现Comparable,然后实现compareTo方法,根据自己的需求返回不同的值。是不是和我们之前的方式完全不同,但是它的确是模板方法的具体使用。所以,模板方法的使用可能形式多种多样,只要是遵守模板方法的大义,就像这个例子,静态方法sort通过和Comparable的组合实现了排序,但是中心思想也是被比较的对象来实现了排序中的大小比较的规则。等一下!这个方式用策略模式来称呼它不是更合适么?通过组合,不关心实现!所以不得不说一说他们之间的区别,最大的区别:策略模式实现整个算法,而模板方式的子类只是实现了整个算法中的一部分。像上面的排序,整个排序方法是sort,而compareTo方法只是辅助sort方法而已。
还有一个和它类似的模式,他们之间才是有关联的,那就是工厂方法,工厂方法模式是一种特殊的模板方法,特殊就特殊在工厂方法模式的方法是为了专门实例化需要的类。