一、含义
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。通俗讲,就是当你的程序中,有多个类,方法是相同的,只是具体实现有差异时,就可以把这多个类使用模板方法模式进行封装。
二、要点
1.模板方法只定义了算法的步骤,把这些步骤的具体实现延迟到子类中进行。
2.模板方法的抽象类可以定义具体方法、抽象方法和钩子;抽象方法教给子类去实现。
3.钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它。
4.策略模式和模版模式有些相同,但策略是使用组合,模板是使用继承。
三、实战分析模板方法模式
老规矩,让我们先来看看这个设计模式的类图:
作为一个程序猿,晚上加班是不可避免的,有的时候加班熬不住,就需要借助外物来提提神,有的人喜欢喝茶提神,有的人喜欢喝咖啡来提神,这个时候就需要设计两个类,一个是茶,一个是咖啡,代码如下:
//咖啡类
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();
}
}
测试结果如下:
prepare Tea:
Boiling water
Steeping the tea
Pouring into cup
需要加柠檬片嘛?(y/n)
y
Adding Lemon
prepare Coffee:
Boiling water
Dripping Coffee through filter
Pouring into cup
Adding Sugar and Milk