模板方法模式

模板方法模式

模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤 延迟到子类中。模板方法可以使得子类在不改变算法结构的前提 下,重新 定义算法的某些步骤。

开发原则 #

  • 针对接口编程,而不是针对实现编程
  • 多用组合,少用继承
  • 为交互对象之间的松耦合而努力
  • 类应该对拓展开放,而对修改关闭 (开放-关闭原则)
  • 依赖抽象,而不依赖具体类 (依赖倒置原则)
  • "最少知识原则"——只和你的朋友交谈,不要让太多的类耦合在一起
  • "好莱坞原则"——别调用我们,我们会调用你

好莱坞原则:定义 #

好莱坞原则定义了一种防止"依赖腐败"的方法。当高层组件依赖底层组件时, 而低层组件又依赖高层组件,而高层组件又依赖边侧组件,边侧组件又 依赖低层组件时,依赖腐败就发生了。

简而言之,「好莱坞原则」希望系统设计能规避环形依赖。

在好莱坞原则上,高层组件依赖低层组件(违反依赖倒置原则),但是 高层组件决定什么时候如何使用低层组件,而低层组件不允许调用 高层组件。

xx

好莱坞原则:和模板方法的关系 #

模板方法运用了好莱坞原则,模板方法需要子类来实现具体细节。只有在需要 子类来实现细节时,才会调用子类的方法。而子类如果没有主动调用,绝对 不会调用抽象类。

工厂方法,观察者模式也运用了「好莱坞模式」。

在模板方法中使用钩子(Hook) #

在模板方法中,还有一种特殊的运用——钩子。利用钩子,可以使子类实现 细节的策略更加灵活。

钩子利用在模板内实现,是否覆盖由子类决定。

UML简图 #

classDiagram
direction RL
class AbstractTemplate {
    << Abstract >>
    +final templateMethod()
    +step1()*
    +step2()*
    +hook()
}
ProgressA --|> AbstractTemplate
class ProgressA{
    +step1()
    +step2()
}
ProgressB --|> AbstractTemplate
class ProgressB{
    +step1()
    +step2()
}

要点 #

  • 模板方法定义了算法的步骤,并且把步骤的实现延迟到子类。
  • 利用模板方法,代码可以很好的复用。
  • 模板方法的抽象类可以定义具体方法,抽象方法和钩子。
  • 钩子是一种方法,它在模板方法中不做事,或者只做默认的事情。
  • 模板方法可以声明为final,以防止子类篡改算法逻辑。
  • 好莱坞原则允许高层组件依赖低层组件,但是低层组件不能调用 高层组件的方法,必须由高层组件决定何时、如何调用。
  • 策略模式和模板方法都是封装算法,前者使用组合,后者使用继承。
  • 工厂方法是模板方法的特殊版本。

示例代码 #

在本次的示例中,假如你需要制作几种不同的饮品,饮品制作有一些通用步骤,比如烧水,加入原料,假如配料,倒出等等,其中烧水和导入饮品杯是完全一样的操作,不同的饮品可能原料和配料有所区别。你不想你的代码出现太多的冗余,可以使用模板方法来实现。

定义模板方法 #

 1public abstract class AbsCaffeineBeverageWithHook {
 2    //    模板方法声明为final,防止子类继承-修改算法
 3    public final void prepareRecipe() {
 4        boilWater();
 5        raw();
 6        condiment();
 7        pullInCup();
 8    }
 9
10    protected final void boilWater() {
11        System.out.println("Starting boiling water.");
12    }
13    protected final void pullInCup() {
14        System.out.println("Done! pull beverage into cup.");
15    }
16
17    public abstract void raw();
18    public abstract void condiment();
19
20    // 这是一个钩子
21    public boolean addCondiment() {
22        return true;
23    }
24}

请注意,示例中的模板方法中存在一个“钩子(Hook)”,它可以让饮料的制作更加灵活,一会你就知道了~

定义不同的实现 #

模板方法定义好了之后,需要针对饮品来制作“配方”。假如我们需要制作茶和咖啡:

 1public class Tea extends AbsCaffeineBeverageWithHook{
 2    // 茶里面的咖啡因含量是咖啡豆的3倍!
 3    @Override
 4    public void raw() {
 5        System.out.println("Put tea bag.");
 6    }
 7
 8    @Override
 9    public void condiment() {
10        System.out.println("Add some lemon juice.");
11    }
12}
13
14public class CoffeeWithHook extends AbsCaffeineBeverageWithHook {
15    @Override
16    public void raw() {
17        System.out.println("Brew coffee Grinds and add it.");
18    }
19
20    @Override
21    public void condiment() {
22        if (addCondiment()) {
23            System.out.println("Add some milk and sugar.");
24        }
25    }
26
27    @Override
28    public boolean addCondiment() {
29        return condimentOrNot().equals("y");
30    }
31
32    private String condimentOrNot() {
33        String str;
34        System.out.println("Would you like to add some milk and sugar(y/n)?");
35        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
36        try {
37            str = in.readLine();
38        } catch (IOException e) {
39            throw new RuntimeException(e);
40        }
41        return str == null ? "n" : str;
42    }
43}

注意,咖啡的制作配方中,重写的模板方法中的addCondiment方法,它会询问顾客是否需要添加配料,毕竟,喜欢黑咖啡的顾客还是不少的~

测试代码 #

 1public class TestTemplateMethod {
 2    public static void main(String[] args) {
 3//        test();
 4        testHook();
 5    }
 6
 7    static void test() {
 8        Coffee coffee = new Coffee();
 9        coffee.prepareRecipe();
10        System.out.println("---------------");
11        Tea tea = new Tea();
12        tea.prepareRecipe();
13    }
14
15    static void testHook() {
16        CoffeeWithHook coffeeWithHook = new CoffeeWithHook();
17        coffeeWithHook.prepareRecipe();
18    }
19}

小结 #

Generated by Gemini, revised.

模板方法模式是一种行为型设计模式,它在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

  1. 核心思想:

    • 定义算法骨架: 在父类中定义一个模板方法,该方法定义了算法的执行步骤和顺序。

    • 延迟实现: 将算法中的某些步骤延迟到子类中实现。

    • 避免代码重复: 将公共代码放在父类中,避免子类中出现重复代码。

    • 控制算法结构: 父类控制算法的整体结构,子类只能修改特定的步骤。

  2. 模板方法中的角色:

    • AbstractClass(抽象类): 定义模板方法,该方法定义了算法的骨架。它可以包含抽象方法,这些方法需要在子类中实现。也可以包含具体方法,这些方法会被子类继承。

    • ConcreteClass(具体类): 实现抽象类中的抽象方法,完成算法中特定步骤的具体实现。

  3. 模板方法的优点:

    • 代码复用: 将公共代码放在抽象类中,避免子类中出现重复代码。

    • 控制灵活性: 抽象类控制算法的整体结构,子类只能修改特定的步骤,保证算法的结构不会被随意改变。

    • 扩展性: 易于添加新的算法步骤,只需要创建新的子类并实现相应的抽象方法即可。

    • 符合开闭原则: 通过扩展子类来实现新的功能,而不需要修改抽象类的代码。

  4. 模板方法的缺点:

    • 抽象类本身定义了算法的框架,这使得它不适合于那些算法框架经常变化的情况。 算法框架一旦确定后,修改起来会比较困难。

    • 增加了类的个数: 每个不同的实现都需要创建一个新的子类,这可能导致类的数量增加,使系统更加复杂。

  5. 适用场景:

    • 多个类具有相似的算法结构,但某些步骤的实现方式不同。

    • 需要控制算法的执行顺序,并防止子类改变算法的结构。

    • 需要将公共代码提取到父类中,避免子类中出现重复代码。

    • 框架设计,框架定义算法流程,具体实现由用户提供。

  6. 与其他模式的关系:

    • 策略模式: 模板方法模式和策略模式都用于封装算法。不同之处在于,模板方法模式定义了算法的骨架,而子类只能修改特定的步骤;策略模式则允许客户端在运行时选择不同的算法。

    • 工厂方法模式: 模板方法模式可以使用工厂方法模式来创建算法中需要的对象。

模板方法模式是一种非常有用的设计模式,它可以帮助你提取公共代码、控制算法结构、并提高代码的可维护性和可扩展性。 理解其核心思想、参与者、优缺点以及适用场景,可以帮助你更好地应用模板方法模式解决实际问题。 关键在于识别出具有相似算法结构,但某些步骤实现不同的场景,并合理地将算法骨架放在抽象类中,将具体实现延迟到子类中。