模板方法模式:在一个方法中定义一个算法的步骤,而将一些步骤延迟到子类中。模板方法使得子类在不改变算法结构的情况下,重新定义算法的某些步骤。
下面用一个例子来详细解释一下。
有些人没有咖啡就活不下去,有些人离不开茶,两者共同的成分是什么?当然是咖啡因了。但,还不只是这样,茶和咖啡的冲泡方式非常相似,不信你瞧瞧:
星巴兹咖啡冲泡法:
(1) 把水煮沸
(2) 用沸水冲泡咖啡
(3) 把咖啡倒进杯子
(4) 加糖和牛奶
星巴兹茶冲泡法:
(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 addLemon() {
System.out.println("Add lemon");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
}
我们发现了重复的代码,这是好现象。这表示我们需要清理一下设计了。在这里,既然茶和咖啡师如此的相似,似乎我们应该把共同的部分抽出来,放进一个基类里。
看起来这个咖啡和茶的设计练习是相当的直接,你的第一版设计看起来是这样:
CaffeineBeverage:
prepareRecipe();
boilWater();
pourInCup();
Coffee:
prepareRecipe();
brewCoffeeGrinds();
addSugarAndMilk();
Tea
prepareRecipe();
steepTeaBag();
addLemon();
我们的新设计你 觉得怎么样?是不是觉得忽略了某些其他共同点?咖啡和茶还有什么是相似的?我们先从冲泡法入手
(1) 把水煮沸
(2) 用沸水冲泡咖啡
(3) 把咖啡倒进杯子
(4) 加糖和牛奶
(1)、(3)我们已经抽出来放到基类里了,(2)、(4)并没有被抽出来,但是他们是一样的,只是应用在不同的饮料上。那么我们有办法将prepareRecipe()也抽象化吗?是的,现在就来看看该怎么做,让我们从每一个子类中逐步抽象prepareRecipe(),我们遇到的第一个问题,就是咖啡使用brewCoffeeGrinds()和addSugarAndMilk()方法,而茶使用的是steepTeaBag()和addLemon()方法。
让我们来思考这一点,浸泡(steep)和冲泡(brew)差异其实不大,所以我们给他一个新的方法名称,比方说brew(),然后不管泡茶还是冲泡咖啡我们都用这个名称。类似的,加糖和牛奶也和加柠檬很相似,都是在饮料中加入调料,让我们也用一个新的方法名称来解决这个问题,就叫做addCondiments()好了。这样一来,新的prepareRecipe()方法看起来就是这样:
void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
现在我们有了新的prepareRecipe()方法,但是需要让他能够符合代码。要想这么做,我们就先从CaffeineBeverage()超类开始:
public abstract class CaffeineBeverage {
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
abstract void brew();
abstract void addCondiments();
public void boilWater() {
System.out.println("Boiling water");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
}
最后我们就需要处理咖啡和茶类了,这两个类现在都是依赖超类来处理冲泡法,所以只需自行处理冲泡和添加调料部分:
public class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("Steeping the tea");
}
public void addCondiments() {
System.out.println("Adding Lemon");
}
}
public class Coffee extends CaffeineBeverage {
public void brew() {
System.out.println("Dripping coffee through filter");
}
public void addCondiments() {
System.out.println("Adding Sugar And Milk");
}
}
基本上,我们刚刚实现的就是模板方法模式。模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
从上面我们可以看出,模板方法模式的优势就是:
1、可以将代码的复用最大化。
2、算法只存在于一个地方,所以容易修改。
3、这个模板提供了一个框架,可以让其他的咖啡因饮料插进来。
4、CaffeineBeverage类专注在算法本身,而由子类提供完整的实现。