抽象算法
假设现在我们有一个Coffe类和一个Tea类,他们分别实现了各自的饮料冲调方法
public class Coffee {
public void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugar();
}
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 addSugar() {
System.out.println("Adding Sugar and Milks");
}
}
public class Tea {
void prepareRecipe() {
boilWater();
steepTeaBag();
addLemon();
pourInCup();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void steepTeaBag() {
System.out.println("Stepping the tea");
}
public void addLemon() {
System.out.println("Adding Lemon");
}
public void pourInCup() {
System.out.println("Pouring into Cup");
}
}
观察类结构,可以发现两种饮料都有共同的冲调流程(即preapareRecipe()方法)基本一致方法,其中boilWater()和pourInCup()是相同的,而剩余的方法虽然具体实现不同,但其实是类似的;所以我们可以将preapareRecipe()这方法稍作改变并抽取出来放到一个抽象超类中,超类提供boilWater()、pourInCup()的实现,同时将剩余两个方法设为抽象,要求其子类实现,从而得出下面的代码
public abstract class CaffeineBeverage {
// 抽取出相同的流程,并设为final,防止子类改变
public final void prepareRecipe() {
boilWater();
brew();
addCondiments();
pourInCup();
}
public void boilWater() {
System.out.println("Boiling water");
}
// 将原来的steepTeaBag和brewCoffeeGrinds抽取为brew方法,并要求子类实现
public abstract void brew();
// 将原来的addSugar和addLemon方法抽取为addCondiments,并要求子类实现
public abstract void addCondiments();
public void pourInCup() {
System.out.println("Pouring into Cup");
}
}
public class Tea extends CaffeineBeverage{
public void brew() {
System.out.println("Stepping 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 Milks");
}
}
测试
public class TemplateTest {
public static void main(String[] args) {
Coffee coffee = new Coffee();
coffee.prepareRecipe();
System.out.println();
Tea tea = new Tea();
tea.prepareRecipe();
}
}
输出
Boiling waterDripping Coffee through filter
Adding Sugar and Milks
Pouring into Cup
Boiling water
Stepping the tea
Adding Lemon
Pouring into Cup
模板方法模式
其实在上一个例子中,抽取出来的CaffeineBeverage抽象超类中的preapaerRecipe()就是一个模板方法,它作为一个算法的模板,在其中将每一个步骤用一个方法代表。这些方法有的由超类提供并处理,有的被设为抽象方法,必须由子类实现并处理。也就是模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
这种模式的的好处是
- 模板方法类主导一切,它拥有算法并保护这个算法
- 对于子类来说,模板方法类可将代码复用最大化
- 算法只存在一个地方,容易修改
- 提供了一个框架,可以让其他相关对象作为子类插入进来,其只需要实现自己的方法即可
- 专注在算法本身,而由子类提供完整的实现
定义
模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
可见,模板方法在实现算法的过程中,用到了两个原语操作(primitiveOperation),模板方法本身与这两个操作的具体实现之间解耦
一般形式
// 声明为抽象类,要求子类实现其操作
public abstract class AbstractClass {
// 模板方法声明为final,避免子类更改
final void templateMethod() {
primitiveOperation1();
primitiveOperation2();
concreteOperation();
hook();
}
// 原语操作是抽象方法,子类必须实现
abstract void primitiveOperation1();
abstract void primitiveOperation1();
// 声明为final的具体方法,可被子类使用,但不能修改
final void concreteOperation() {
// 实现
}
// 一个什么都不做的具体方法,这种方法被称为“钩子”
void hook() {}
}
钩子 - hook
钩子是一种被生命在抽象类中的方法,但只有空或默认的实现,其存在是让子类有能力对算法的不同点进行挂钩,而要不要挂钩,由子类自行决定。如下例子,customerWantsCondiments()就是一个钩子,它控制了模板方法中是否执行某部分的算法
public abstract class CaffeineBeverageWithHook {
public final void prepareRecipe() {
boilWater();
brew();
// 条件语句使用钩子判断
if(customerWantsCondiments()) {
addCondiments();
}
addCondiments();
pourInCup();
}
public void boilWater() {
System.out.println("Boiling water");
}
public abstract void brew();
public abstract void addCondiments();
public void pourInCup() {
System.out.println("Pouring into Cup");
}
// 一个钩子,子类可以覆盖这个方法,但不是必须的
public boolean customerWantsCondiments() {
return true;
}
}
所以,当子类必须提供算法中某个方法或步骤的实现时,该方法应该声明为抽象的。相反,当算法某个方法是可选的,就使用钩子,子类可以选择实现或不实现这个钩子
好莱坞原则
好莱坞原则:别调用我们,我们会调用你
好莱坞原则可以防止“依赖腐败”(高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件又依赖低层组件)
好莱坞原则之下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件,即对于高层组件对待低层组件的方式是:别调用我,我会调用你们
在之前的CaffeineBeverage例子中,CaffeineBeverage就是一个高层组件,它控制了算法的执行,只有在需要子类实现某个方法时,才会调用子类