背景:
我们要实现冲泡饮料的程序:
泡茶的过程为:1)把水煮沸 2)用沸水浸泡茶叶 3)把茶倒进杯子 4)加柠檬;
冲咖啡的过程为:1)把水煮沸 2)用沸水冲泡咖啡 3)把咖啡倒进杯子 4)加糖和咖啡。
设计:
冲饮料的过程大致一样,所以我们可以将共同的部分取出来放进基类里,不同的部分尽量抽象出来,不能抽象的就算了。
所以基类设计为:
// 模版方法
void prepareRecipe() {
boilWater(); // 倒水
brew(); // 冲泡 抽象方法
pourInCup(); // 倒进杯子
addCondiments(); //加配料 抽象方法
}
好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。
优点:防止依赖腐败。我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候使用这些低层组件。换句话说:别调用我们,我们会调用你。实现这个原则的还有很多模式:工厂模式、观察者模式等。
此外,好莱坞原则也不是不能调用高层组件,我们往往会在低层组件结束时调用从超类中继承来的方法。我们所要做的是避免让高层和低层组件之间有明显的环状依赖。
所以好莱坞原则告诉我们,将决策权放在高层模块中,以便决定如何以及合适调用低层模块。
实现:
// 基类
public abstract class Beverage {
// 定义为final方法防止子类覆盖这个方法
final void prepareRecipe() {
boidWater();
brew();
pourInCup();
addCondiments();
}
// 定义为抽象方法 剩下的交给子类去操心
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("Boiling water");
}
void pourCup() {
System.out.println("Pouring into cup");
}
}
// Tea 实现两个抽象方法,剩下的都一样
public class Tea extends Beverage {
public void brew() {
System.out.println("Steeping the tea");
}
public void addCondiments() {
System.out.println("Adding Lemon");
}
}
// Coffee 实现两个抽象方法,剩下的都一样
public class Coffee extends Beverage {
public void brew() {
System.out.println("Dripping Coffee through filter");
}
public void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}
// 主类
public class MakeBeverage {
public static void main(String[] args){
Beverage tea = new Tea();
tea.prepareRecipe(); // 调用父类方法
Beverage coffee = new Coffee();
coffee.prepareRecipe();
}
}
模版方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
基类主导一切,拥有算法并且保护了这个算法,同时最大化代码复用。
模版方法提供了一个框架,可以让其他的饮料插进来,新的饮料只需实现自己的饮料即可。
钩子方法:在基类中定义一个具体的方法,子类可以视情况选择覆盖或不覆盖它。
// 基类
public abstract class Beverage {
// 定义为final方法防止子类覆盖这个方法
final void prepareRecipe() {
boidWater();
brew();
pourInCup();
if(customerWantsCondiments()) { // 默认是加调料,也可以不加
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("Boiling water");
}
void pourCup() {
System.out.println("Pouring into cup");
}
// 这就钩子方法,子类可以覆盖这个方法
boolean customerWantsCondiments() {
return true;
}
}
钩子方法可以让子类实现算法中可选的部分,也可以让子类能够有机会对模版方法中某些即将发生的步骤作出反应(比如说排序)。
在sort()方法中,我们有时需要实现compareTo()来实现排序,虽然这不太像是模版方法,但是荒野中的模式并非总是中规中矩的,为了符合当前的环境和实现约束是可以被适当的修改,它的实现精神也符合模版方法。
排序的实现是不是有点像策略模式,但是策略模式是使用对象的组合,所组合的类实现了整个算法,而我们的数组所实现的排序算法并不完整,它需要被填补compareTo()方法的实现,所以它更像模版方法模式。
总结:
模版方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以在不改变算结构的情况下,重新定义算法中的某些步骤。
在切割模版方法中的算法内的步骤时,不要切割的太细,但如果步骤太少会没有弹性,所以要看情况进行折衷。
好莱坞原则和依赖倒置原则的比较:
- 依赖倒置原则:尽量避免使用具体类,而是多使用抽象。
- 好莱坞原则:用在创建框架或组件上的一种技巧,好让低层组件能够被挂钩进计算中,而且又不会让高层组件依赖低层组件。
两者的目标都是在于解藕,但是依赖倒置原则更加注重如何在设计中避免依赖。
模版方法模式和策略模式的比较:
- 模版方法:定义一个算法大纲,由子类实现某些步骤的内容,算法的结构维持不变,而算法的内部实现细节可变;
- 策略模式:定一个算法家族并让这些算法可以互换,因为每个算法都被封装起来了,所以客户可以轻易地使用不同的算法。
模版方法通过继承抽象类进行算法实现,策略模式通过对象的组合方式让客户可以选择算法的实现。
工厂方法是模版方法的一种特殊的版本。