在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方式使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。通俗的讲,就是当你的程序中,有多个类,方法是相同的,只是具体实现有差异时,就可以把这多个类使用模版方法模式进行封装,也就是将通用的步骤抽离出来,定义成一套可以多次使用的模版。
要点
-
模版方法中只定义了算法的步骤,把这些步骤的具体实现延迟到子类中进行。
-
模板方法的抽象类可以定义具体方法、抽象方法和钩子;抽象方法交给子类去实现。
-
钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要覆盖它。
模版方法模式类图
在java源码中:在j.U.C包中,针对ReentrantLock和ReenTrantReadWriteLock就使用了模版方法将通用的部分,封住出了AQS,针对上图AQS就是AbstractClass,而ConcreteClass就分别是ReentrantLock和ReenTrantReadWriteLock,因为ReenTrantReadWriteLock中,写锁的实现和ReenTrantLock的中的锁实现基本是一致的。
实例分析
作为一个程序猿,晚上加班是不可避免的,有的时候加班熬不住了,就需要借助外物来提提神,有的人喜欢喝茶提神,有的人喜欢喝咖啡来提神,这个时候就需要设计两个类,一个是茶,一个是咖啡,代码如下:
//咖啡类
public class Coffee {
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
//把水煮沸
public void boilWater() {
System.out.println("Boiling water");
}
//用沸水冲泡咖啡
public void brewCoffeeGrinds() {
System.out.println("Dripping Coffee through filter");
}
//把咖啡倒进杯子
public void pourInCup() {
System.out.println("Pouring into cup");
}
//加糖和牛奶
public void addSugarAndMilk() {
System.out.println("Adding Sugar and Milk");
}
}
//茶类
public class Tea {
void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
//把水煮沸
public void boilWater() {
System.out.println("Boiling water");
}
//泡茶叶
public void steepTeaBag() {
System.out.println("Steeping the tea");
}
//把茶倒进杯子
public void pourInCup() {
System.out.println("Pouring into cup");
}
//加柠檬
public void addLemon() {
System.out.println("Adding Lemon");
}
}
从这两个类中可以看出,其实泡茶和泡咖啡都需要把水煮沸,倒进杯子,只是一个是加咖啡,牛奶,一个是加茶叶、加柠檬,根据模版模式,可以将这两个对象进行处理,抽离一个超类作为模版
//这是一个模板
public abstract class drinks {
//将这个方法声明为final,不希望子类覆盖这个方法
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
//通用的方法直接在父类中实现,在这个例子中,就是烧水和倒进杯子
public void boilWater() {
System.out.println("Boiling water");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
//需要由子类的去实现的方法,定义为抽象方法
abstract void brew();
abstract void addCondiments();
}
在重新定义茶和咖啡的类:
//新的茶类继承饮料
public class Tea extends Drinks {
//将父类中的抽象方法具体化,来满足自己的实现
@Override
void brew() {
System.out.println("Steeping the tea");
}
@Override
void addCondiments() {
System.out.println("Adding Lemon");
}
}
//新的咖啡类继承自模板
public class Coffee extends Drinks {
//将父类中的抽象方法具体化,来满足自己的实现
@Override
void brew() {
System.out.println("Dripping Coffee through filter");
}
@Override
void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}
但是本人在喝茶的时候,不喜欢放柠檬片,只想保留茶的原汁原味,但是其他人,可能和我的口味不一样,那么这时就需要根据个人喜好去决定到底要不要加柠檬片,这个时候就可以使用,模版方法中的钩子,那么什么是钩子呢?
钩子是一种被声明在抽象类中的方法,但只有空和默认的实现。钩子的存在,可以让子类有能力对算法中不同点进行挂钩。需不需要挂钩由子类自行决定。
重新定义一下模板类:
//这是一个模板
public abstract class Drinks {
//将这个方法声明为final,不希望子类覆盖这个方法
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerDecision()) {
addCondiments();
}
}
//通用的方法直接在父类中实现,在这个例子中,就是烧水和倒进杯子
public void boilWater() {
System.out.println("Boiling water");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
//需要由子类的去实现的方法,定义为抽象方法
abstract void brew();
abstract void addCondiments();
//这是一个钩子,子类可以决定是否覆盖这个方法
boolean customerDecision() {
return true;
}
}
在修改一下茶的类,加入钩子的实现
//新的茶类继承饮料
public class Tea extends Drinks {
//将父类中的抽象方法具体化,来满足自己的实现
@Override
void brew() {
System.out.println("Steeping the tea");
}
@Override
void addCondiments() {
System.out.println("Adding Lemon");
}
//覆盖钩子实现自己的定义
@Override
boolean customerDecision() {
String answer = getUserImput();
if (answer.toLowerCase().startsWith("y")) {
return true;
}
return false;
}
private String getUserImput() {
String answer = null;
System.out.println("需要加柠檬片嘛?(y/n)");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch (Exception e) {
System.out.println(e.getMessage());
}
if (answer == null) {
return "no";
}
return answer;
}
}
最和来一个测试方法:
public class DrinksTest {
public static void main(String[] args) {
//创建茶和咖啡
Tea tea = new Tea();
Coffee coffee = new Coffee();
System.out.println("prepare Tea:");
tea.prepareRecipe();
System.out.println("prepare Coffee:");
coffee.prepareRecipe();
}
}
本文部分内存参考《Head First 设计模式》
品略图书馆 http://www.pinlue.com/