模板方法模式
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤 延迟到子类中。模板方法可以使得子类在不改变算法结构的前提 下,重新 定义算法的某些步骤。
开发原则 #
- 针对接口编程,而不是针对实现编程
- 多用组合,少用继承
- 为交互对象之间的松耦合而努力
- 类应该对拓展开放,而对修改关闭 (开放-关闭原则)
- 依赖抽象,而不依赖具体类 (依赖倒置原则)
- "最少知识原则"——只和你的朋友交谈,不要让太多的类耦合在一起
- "好莱坞原则"——别调用我们,我们会调用你
好莱坞原则:定义 #
好莱坞原则定义了一种防止"依赖腐败"的方法。当高层组件依赖底层组件时, 而低层组件又依赖高层组件,而高层组件又依赖边侧组件,边侧组件又 依赖低层组件时,依赖腐败就发生了。
简而言之,「好莱坞原则」希望系统设计能规避环形依赖。
在好莱坞原则上,高层组件依赖低层组件(违反依赖倒置原则),但是 高层组件决定什么时候如何使用低层组件,而低层组件不允许调用 高层组件。
好莱坞原则:和模板方法的关系 #
模板方法运用了好莱坞原则,模板方法需要子类来实现具体细节。只有在需要 子类来实现细节时,才会调用子类的方法。而子类如果没有主动调用,绝对 不会调用抽象类。
工厂方法,观察者模式也运用了「好莱坞模式」。
在模板方法中使用钩子(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.
模板方法模式是一种行为型设计模式,它在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
核心思想:
定义算法骨架: 在父类中定义一个模板方法,该方法定义了算法的执行步骤和顺序。
延迟实现: 将算法中的某些步骤延迟到子类中实现。
避免代码重复: 将公共代码放在父类中,避免子类中出现重复代码。
控制算法结构: 父类控制算法的整体结构,子类只能修改特定的步骤。
模板方法中的角色:
AbstractClass(抽象类): 定义模板方法,该方法定义了算法的骨架。它可以包含抽象方法,这些方法需要在子类中实现。也可以包含具体方法,这些方法会被子类继承。
ConcreteClass(具体类): 实现抽象类中的抽象方法,完成算法中特定步骤的具体实现。
模板方法的优点:
代码复用: 将公共代码放在抽象类中,避免子类中出现重复代码。
控制灵活性: 抽象类控制算法的整体结构,子类只能修改特定的步骤,保证算法的结构不会被随意改变。
扩展性: 易于添加新的算法步骤,只需要创建新的子类并实现相应的抽象方法即可。
符合开闭原则: 通过扩展子类来实现新的功能,而不需要修改抽象类的代码。
模板方法的缺点:
抽象类本身定义了算法的框架,这使得它不适合于那些算法框架经常变化的情况。 算法框架一旦确定后,修改起来会比较困难。
增加了类的个数: 每个不同的实现都需要创建一个新的子类,这可能导致类的数量增加,使系统更加复杂。
适用场景:
多个类具有相似的算法结构,但某些步骤的实现方式不同。
需要控制算法的执行顺序,并防止子类改变算法的结构。
需要将公共代码提取到父类中,避免子类中出现重复代码。
框架设计,框架定义算法流程,具体实现由用户提供。
与其他模式的关系:
策略模式: 模板方法模式和策略模式都用于封装算法。不同之处在于,模板方法模式定义了算法的骨架,而子类只能修改特定的步骤;策略模式则允许客户端在运行时选择不同的算法。
工厂方法模式: 模板方法模式可以使用工厂方法模式来创建算法中需要的对象。
模板方法模式是一种非常有用的设计模式,它可以帮助你提取公共代码、控制算法结构、并提高代码的可维护性和可扩展性。 理解其核心思想、参与者、优缺点以及适用场景,可以帮助你更好地应用模板方法模式解决实际问题。 关键在于识别出具有相似算法结构,但某些步骤实现不同的场景,并合理地将算法骨架放在抽象类中,将具体实现延迟到子类中。