(点击
上方公众号
,可快速关注)
来源:cashow,
cashow.github.io/head-first-design-patterns-notes-template-method-pattern.html
如有好文章投稿,请点击 → 这里了解详情
模板方法模式在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
有些人没有咖啡就活不下去;有些人则离不开茶。两者共同的成分是什么?当然是咖啡因了!
但还不只这样。茶和咖啡的冲泡方式非常相似:
星巴兹咖啡冲泡法
-
把水煮沸
-
用沸水冲泡咖啡
-
把咖啡倒进杯子
-
加糖和牛奶
星巴兹茶冲泡法
-
把水煮沸
-
用沸水冲泡茶叶
-
把茶倒进杯子
-
加柠檬
下面我们用代码来创建咖啡和茶:
// 这是我们的咖啡类,用来煮咖啡
public class Coffee {
// 这是我们的咖啡冲泡法
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugerAndMilk();
}
// 煮沸水
private void boilWater() {
System.out.println("Boiling water");
}
// 冲泡咖啡
private void brewCoffeeGrinds() {
System.out.println("Dripping coffee through filter");
}
// 把咖啡倒进杯子
private void pourInCup() {
System.out.println("Pouring into cup");
}
// 加糖和奶
private void addSugerAndMilk() {
System.out.println("Adding Sugar and Milk");
}
}
// 这是我们的茶类,用来煮茶
public class Tea {
void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
// 煮沸水。这个方法和咖啡类完全一样
private void boilWater() {
System.out.println("Boiling water");
}
// 冲泡茶叶
private void steepTeaBag() {
System.out.println("Steeping the tea");
}
// 把茶倒进杯子。这个方法和咖啡类完全一样
private void pourInCup() {
System.out.println("Pouring into cup");
}
// 加柠檬
private void addLemon() {
System.out.println("Adding Lemon");
}
}
我们发现了重复的代码,这表示我们需要清理一下设计了。在这里,茶和咖啡是如此得相似,似乎我们应该将共同的部分抽取出来,放进一个基类中。
第一版设计
看起来这个咖啡和茶的设计相当简单,你的第一版设计,可能看起来像这样:
public abstract class CaffeineBeverage {
// prepareRecipe()方法在每个类中都不一样,所以定义成抽象方法。
abstract void prepareRecipe();
// 以下两个方法被两个子类所共享,所以被定义在这个超类中
public void boilWater() {
System.out.println("Boiling water");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
}
public class Coffee extends CaffeineBeverage {
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugerAndMilk();
}
private void brewCoffeeGrinds() {
System.out.println("Dripping coffee through filter");
}
private void addSugerAndMilk() {
System.out.println("Adding Sugar and Milk");
}
}
public class Tea extends CaffeineBeverage {
void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
private void steepTeaBag() {
System.out.println("Steeping the tea");
}
private void addLemon() {
System.out.println("Adding Lemon");
}
}
更进一步的设计
以上的设计是不是忽略了某些其他的共同点?咖啡和茶之间还有什么是相似的?
注意到两份冲泡法都采用了相同的算法:
1. 把水煮沸
2. 用热水泡咖啡或茶
3. 把饮料倒进杯子
4. 在饮料中加入适当的调料
其中,第2步和第4步并没有被抽取出来,但他们是一样的,只是应用在了不同的饮料上。我们有办法把prepareRecipe()方法也抽象化吗?让我们先从每一个子类中逐步抽象prepareRecipe()。
抽象prepareRecipe()
我们遇到的第一个问题就是,咖啡使用brewCoffeeGrinds()和addSugerAndMilk()方法,而茶使用steepTeaBag()和addLemon()方法。让我们思考这一点:浸泡(steep)和冲泡(brew)差异其实不大。所以我们给它一个新的方法名称,比方说brew(),然后不管是泡茶或者冲泡咖啡我们都用这个名称。类似地,加糖和牛奶都是在饮料中加入调料。让我们也给它一个新的方法名称:addCondiments()。这样一来,新的prepareRecipe()方法看起来像是这样:
void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
现在我们有了新的prepareRecipe()方法,但是需要让它能够符合代码。要想这么做,我们先从CaffeineBeverage超类开始:
public abstract class CaffeineBeverage {
// 现在,用同一个prepareRecipe()方法来处理茶和咖啡。
// prepareRecipe()方法被声明为final,因为我们不希望子类覆盖这个方法
// 我们将第2步和第4步泛化成为brew()和addCondiments()
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
// 因为咖啡和茶处理这些方法的做法不同,所以这两个方法必须被声明为抽象,