引入
定义:在一个方法中定义了一个算法的骨架,而将一些一些步骤延迟到子类中。模板方法使得子类可以在不改变算法接口的情况下,重新定义算法中的某些步骤。
uml类图
这个模式是用来创建一个算法的模板,什么是模板?如你所见的,模板就是一个方法。更具体地说,这个方法将算法定义成一组步骤,其中任何步骤都可以使抽象的,由子类负责实现,这样可以确保算法的结构保持不变,同时由子类提供部分实现。
示例
引用head first上的一个例子"咖啡冲泡手册"
咖啡冲泡法
1.把水煮沸
2.用沸水冲泡咖啡
3.把咖啡倒进杯子
4.加糖和牛奶
茶水冲泡法
1.把水煮沸
2.把沸水浸泡茶叶
3.把茶倒进杯子
4.加柠檬
我们可以看出两种冲泡方法,流程基本一致,我们将相同的行为提取出来,将类似的行为向上泛化,有子类完成具体的实现
抽象后:
1.把水煮沸
2.冲泡
3.倒进杯子
4.加料
package com.zpkj.project16;
public abstract class CaffeineBeverage {
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
addCondiments();
}
//将下层行为向上抽象
abstract void brew();
abstract void addCondiments();
void boilWater(){
System.out.println("Boiling water");
}
void pourInCup(){
System.out.println("Pouring into cup");
}
}
package com.zpkj.project16;
public class Coffee extends CaffeineBeverage{
@Override
void brew() {
System.out.println("Dripping Coffee through filter");
}
@Override
void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}
package com.zpkj.project16;
public class Tea extends CaffeineBeverage{
@Override
void brew() {
System.out.println("Steeping the tea");
}
@Override
void addCondiments() {
System.out.println("Adding lemon");
}
}
package com.zpkj.project16;
public class Cilent {
public static void main(String[] args) {
CaffeineBeverage beverage1 = new Tea();
CaffeineBeverage beverage2 = new Coffee();
beverage1.prepareRecipe();
System.out.println("-------------------");
beverage2.prepareRecipe();
}
}
结果
使用场景:
多个子类有共有的方法,并且逻辑基本相同时; 重要、复杂的算法,可以把核心算法设计成模板方法,周边相关细节功能则由各个子类实现; 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为。
好处:
1.代码复用
2.保证算法结构不变,无需关心具体的实现
使用钩子
当子类必须提供算法中的实现时,就可以使用抽象方法,如果算法中的某个部分是 可选的,子类可以选择实现这个钩子。
改造上面的抽象模板代码
package com.zpkj.project16;
public abstract class CaffeineBeverage {
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
if(isCondiments()){
addCondiments();
}
}
//将下层行为向上抽象
abstract void brew();
abstract void addCondiments();
void boilWater(){
System.out.println("Boiling water");
}
void pourInCup(){
System.out.println("Pouring into cup");
}
boolean isCondiments(){
return true;
}
}
我们想要不加佐料的咖啡,在子类中覆盖钩子方法,
package com.zpkj.project16;
public class Coffee extends CaffeineBeverage{
@Override
void brew() {
System.out.println("Dripping Coffee through filter");
}
@Override
void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
@Override
boolean isCondiments() {
return false;
}
}
结果
oo原则
1::好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。
好莱坞原则给我们一种防止“依赖腐败”的方法。当高层主键依赖底层组件,而底层组件又依赖高层组件,而高层组件又依赖边界组件,边界组件又依赖底层组件时,系统设计就变的非常之复杂。
在好莱坞原则下,允许底层组件将自己挂钩到系统上,高层组件决定怎么使用这些底层组件!
"别调用我们,我们会调用你"
好莱坞原则和依赖倒置原则
1.依赖倒置教我们尽量避免使用具体类,而多实用抽象。
2.好莱坞原则是用在 创建框架或组件上的一种技巧,好让低层组件能够被挂钩到计算中,而不会让高层组件依赖底层组件。
两者的目标都在于解耦
jdk中的模板方法
在Arrays类中,提供了数组操作的排序方法。
public static void sort(Object[] a) {
Object[] aux = (Object[])a.clone();
mergeSort(aux, a, 0, a.length, 0);
}
mergeSort方法包含排序算法,此算法依赖于compartTo()方法的实现来完成算法
模板方法
private static void mergeSort(Object[] src,
Object[] dest,
int low,
int high,
int off) {
int length = high - low;
// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; i<high; i++)
for (int j=i; j>low &&
((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
// Recursively sort halves of dest into src
int destLow = low;
int destHigh = high;
low += off;
high += off;
int mid = (low + high) >>> 1;
mergeSort(dest, src, low, mid, -off);
mergeSort(dest, src, mid, high, -off);
// If list is already sorted, just copy from src to dest. This is an
// optimization that results in faster sorts for nearly ordered lists.
if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
System.arraycopy(src, low, dest, destLow, length);
return;
}
// Merge sorted halves (now in src) into dest
for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}
}
示例
package com.zpkj.array;
public class Duck implements Comparable<Duck>{
String name;
int weight;
public Duck(String name,int weight) {
super();
this.name = name;
this.weight = weight;
}
@Override
public int compareTo(Duck o) {
if(this.weight<o.weight){
return -1;
}else if(this.weight == o.weight){
return 0;
}else{
return 1;
}
}
@Override
public String toString() {
return "Duck [name=" + name + ", weight=" + weight + "]";
}
}
package com.zpkj.array;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Duck duck1 = new Duck("黄鸭子", 6);
Duck duck2 = new Duck("绿鸭子", 3);
Duck duck3 = new Duck("红鸭子", 8);
Duck duck4 = new Duck("紫鸭子", 5);
Duck[] ducks = new Duck[]{duck1,duck2,duck3,duck4};
Arrays.sort(ducks);
for(Duck e:ducks){
System.out.println(e.toString());
}
}
}
结果
总结
模板方法模式用四个字概括就是:流程封装。也就是把某个固定的流程封装到一个 final 函数中,并且让子类能够定制这个流程中的某些或者所有步骤,这就要求父类提取公用的代码,提升代码的复用率,同时也带来了更好的可封装性。
1.模板方法定义了算法的步骤,把这些步骤的实现延迟到子类。
2.模板方法模式为我们提供了一种代码复用的重要技巧
3.模板方法的抽象类可以定义具体方法,抽象方法和钩子
4.抽象方法由子类实现
5.钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择是否覆盖它
6.为防止子类改变模板方法中的算法,可以将模板方法声明为final
7.好莱坞原则告诉我们。将决策权放在高层模板中,以便决定如何以及何时调用低层模板
8.策略模式和模板fang方法模式都封装算法,一个用组合,一个用继承
9.工厂方法是模板方法的一种特殊版本