模板方法模式


目录

  1. 模板方法模式简介

  2. 模板方法模式的意图

  3. 模板方法模式的结构

  4. 模板方法模式的实现

  5. 模板方法模式的适用场景

  6. 模板方法模式的优缺点

  7. 模板方法模式的常见误区与解决方案

  8. 模板方法模式的实际应用实例

  9. 模板方法模式与其他模式的比较

  10. 模板方法模式的扩展与变体

  11. 总结

  12. 参考资料


1. 模板方法模式简介

模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作中的算法骨架,将一些步骤延迟到子类中。模板方法使得子类可以不改变算法结构的情况下,重新定义算法的某些特定步骤。

关键点:

  • 算法骨架:在抽象类中定义算法的结构和步骤。

  • 步骤重定义:子类可以重写抽象类中定义的某些步骤,实现特定的行为。

  • 控制反转:抽象类控制算法的执行顺序,子类仅负责实现具体步骤。


2. 模板方法模式的意图

模板方法模式的主要目的是:

  • 定义算法的骨架:在抽象类中定义算法的基本结构和步骤,确保算法执行的顺序。

  • 允许子类重定义特定步骤:子类可以根据需要重写抽象类中定义的某些步骤,实现不同的行为。

  • 提高代码的复用性:将通用的算法步骤集中在抽象类中,减少代码重复。

  • 遵循开闭原则:通过添加新的子类或重写部分方法,扩展算法的功能,而无需修改现有的代码。


3. 模板方法模式的结构

3.1. 结构组成

模板方法模式主要由以下四个角色组成:

  1. AbstractClass(抽象类):定义算法的骨架和步骤,部分步骤由子类实现。

  2. ConcreteClass(具体类):实现抽象类中定义的某些步骤,完成具体的行为。

  3. Client(客户端):创建具体类的实例,并调用模板方法执行算法。

  4. Primitive Operations(基本操作):抽象类中定义的算法步骤,具体类实现这些步骤。

角色关系:

  • AbstractClass 定义了算法的骨架,并包含一个或多个模板方法。

  • ConcreteClass 继承 AbstractClass,实现具体的算法步骤。

  • Client 通过 AbstractClass 的引用来调用模板方法,无需关心具体的实现细节。

3.2. UML类图

以下是模板方法模式的简化UML类图:

+-------------------------------+
|     AbstractClass             |
+-------------------------------+
| + templateMethod(): void      |
| + primitiveOperation1(): void |
| + primitiveOperation2(): void |
+-------------------------------+
            ^
            |
+-------------------------------+
|    ConcreteClass              |
+-------------------------------+
| + primitiveOperation1(): void |
| + primitiveOperation2(): void |
+-------------------------------+

说明:

  • AbstractClass 定义了 templateMethod(),该方法调用一系列的 primitiveOperation 方法。

  • ConcreteClass 实现了 primitiveOperation1()primitiveOperation2(),定义了具体的行为。

  • Client 通过 AbstractClass 的引用调用 templateMethod(),执行完整的算法。


4. 模板方法模式的实现

模板方法模式的实现需要确保抽象类定义了算法的骨架,具体类实现了具体的步骤。以下示例将展示如何在Java和Python中实现模板方法模式。以一个简单的饮料冲泡过程为例,实现不同类型饮料的冲泡流程。

4.1. Java 实现示例

示例说明

我们将实现一个饮料冲泡系统,支持咖啡和茶两种饮料。通过模板方法模式,定义冲泡饮料的通用流程,并在具体类中实现不同的步骤。

代码实现

// AbstractClass(抽象类)
public abstract class Beverage {
    // 模板方法
    public final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    // 通用步骤
    private void boilWater() {
        System.out.println("Boiling water.");
    }

    private void pourInCup() {
        System.out.println("Pouring into cup.");
    }

    // 需要子类实现的步骤
    protected abstract void brew();
    protected abstract void addCondiments();
}

// ConcreteClassA(具体类 - 咖啡)
public class Coffee extends Beverage {
    @Override
    protected void brew() {
        System.out.println("Dripping coffee through filter.");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding sugar and milk.");
    }
}

// ConcreteClassB(具体类 - 茶)
public class Tea extends Beverage {
    @Override
    protected void brew() {
        System.out.println("Steeping the tea.");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding lemon.");
    }
}

// Client(客户端)
public class TemplateMethodDemo {
    public static void main(String[] args) {
        Beverage coffee = new Coffee();
        System.out.println("\nMaking coffee:");
        coffee.prepareRecipe();

        Beverage tea = new Tea();
        System.out.println("\nMaking tea:");
        tea.prepareRecipe();
    }
}

输出

Making coffee:
Boiling water.
Dripping coffee through filter.
Pouring into cup.
Adding sugar and milk.

Making tea:
Boiling water.
Steeping the tea.
Pouring into cup.
Adding lemon.

代码说明

  • Beverage类(AbstractClass):定义了冲泡饮料的模板方法 prepareRecipe(),该方法包含了冲泡饮料的通用步骤和需要子类实现的步骤。

  • Coffee类(ConcreteClassA):实现了 brew()addCondiments() 方法,定义了咖啡的具体冲泡步骤。

  • Tea类(ConcreteClassB):实现了 brew()addCondiments() 方法,定义了茶的具体冲泡步骤。

  • TemplateMethodDemo类(Client):客户端代码,创建了咖啡和茶的实例,并调用 prepareRecipe() 方法执行冲泡流程。

4.2. Python 实现示例

示例说明

同样,实现一个饮料冲泡系统,支持咖啡和茶两种饮料。通过模板方法模式,定义冲泡饮料的通用流程,并在具体类中实现不同的步骤。

代码实现

from abc import ABC, abstractmethod

# AbstractClass(抽象类)
class Beverage(ABC):
    # 模板方法
    def prepare_recipe(self):
        self.boil_water()
        self.brew()
        self.pour_in_cup()
        self.add_condiments()

    # 通用步骤
    def boil_water(self):
        print("Boiling water.")

    def pour_in_cup(self):
        print("Pouring into cup.")

    # 需要子类实现的步骤
    @abstractmethod
    def brew(self):
        pass

    @abstractmethod
    def add_condiments(self):
        pass

# ConcreteClassA(具体类 - 咖啡)
class Coffee(Beverage):
    def brew(self):
        print("Dripping coffee through filter.")

    def add_condiments(self):
        print("Adding sugar and milk.")

# ConcreteClassB(具体类 - 茶)
class Tea(Beverage):
    def brew(self):
        print("Steeping the tea.")

    def add_condiments(self):
        print("Adding lemon.")

# Client(客户端)
def template_method_demo():
    coffee = Coffee()
    print("\nMaking coffee:")
    coffee.prepare_recipe()

    tea = Tea()
    print("\nMaking tea:")
    tea.prepare_recipe()

if __name__ == "__main__":
    template_method_demo()

输出

Making coffee:
Boiling water.
Dripping coffee through filter.
Pouring into cup.
Adding sugar and milk.

Making tea:
Boiling water.
Steeping the tea.
Pouring into cup.
Adding lemon.

代码说明

  • Beverage类(AbstractClass):定义了冲泡饮料的模板方法 prepare_recipe(),该方法包含了冲泡饮料的通用步骤和需要子类实现的步骤。

  • Coffee类(ConcreteClassA):实现了 brew()add_condiments() 方法,定义了咖啡的具体冲泡步骤。

  • Tea类(ConcreteClassB):实现了 brew()add_condiments() 方法,定义了茶的具体冲泡步骤。

  • template_method_demo函数(Client):客户端代码,创建了咖啡和茶的实例,并调用 prepare_recipe() 方法执行冲泡流程。


5. 模板方法模式的适用场景

模板方法模式适用于以下场景:

  1. 定义算法的骨架:当多个子类需要共享算法的结构,但具体的某些步骤可能不同。

  2. 固定算法的步骤顺序:当算法的步骤顺序是固定的,并且不希望子类改变算法的整体结构。

  3. 行为的部分重定义:当需要让子类在不改变算法结构的情况下,重新定义算法的某些步骤。

  4. 复用代码:通过将通用的算法步骤集中在抽象类中,减少代码重复,提高代码复用性。

  5. 封装变化:当算法中某些步骤可能会变化时,通过策略模式或模板方法模式来封装变化。

示例应用场景:

  • 框架设计:在框架中定义操作的流程,允许用户通过继承和重写特定步骤来定制功能。

  • 数据处理流程:如数据读取、处理、保存等步骤,可以通过模板方法模式定义处理流程,子类实现具体的数据处理逻辑。

  • 文档生成:如PDF生成器,不同类型的文档可能有不同的格式,但生成过程的步骤基本相同。

  • 测试用例设计:在测试框架中定义测试用例的执行步骤,具体的测试内容由子类实现。


6. 模板方法模式的优缺点

6.1. 优点

  1. 代码复用:通用的算法步骤集中在抽象类中,减少了代码的重复,提高了代码的复用性。

  2. 控制算法结构:通过模板方法定义算法的骨架,确保算法步骤的顺序和结构不被子类改变。

  3. 灵活性:子类可以根据需要重写某些步骤,实现特定的行为,提升系统的灵活性和可扩展性。

  4. 遵循开闭原则:通过添加新的子类或重写部分方法,扩展算法的功能,而无需修改现有的代码。

  5. 封装变化:将算法中可能变化的部分封装到子类中,隔离了变化对系统其他部分的影响。

6.2. 缺点

  1. 增加类的数量:每个具体的模板方法需要一个独立的子类,可能导致系统中类的数量增加,增加了设计的复杂性。

  2. 子类之间的依赖:子类需要按照抽象类的定义实现特定的步骤,可能导致子类之间存在一定的依赖关系。

  3. 设计复杂性:设计模板方法模式需要对算法的结构和步骤有清晰的理解,可能增加设计的复杂性。

  4. 难以理解:对于初学者来说,模板方法模式的抽象和继承关系可能较难理解和掌握。


7. 模板方法模式的常见误区与解决方案

7.1. 误区1:过度使用模板方法模式

问题描述: 开发者可能倾向于将所有需要控制算法结构的场景都使用模板方法模式,导致系统中充斥着大量的抽象类和子类,增加了系统的复杂性和维护成本。

解决方案:

  • 评估必要性:仅在确实需要控制算法结构和复用代码的场景下使用模板方法模式。

  • 合理设计模板类:确保抽象类和子类的职责单一,避免职责混乱。

  • 结合其他模式:在适当的情况下,结合使用其他设计模式(如策略模式)实现更灵活的功能。

7.2. 误区2:忽视算法的完整性

问题描述: 有时开发者在实现模板方法模式时,可能忽视算法的完整性,导致某些步骤未被子类正确实现,影响算法的正确性。

解决方案:

  • 提供默认实现:在抽象类中为某些步骤提供默认实现,子类可以选择重写或使用默认。

  • 使用钩子方法:在模板方法中引入钩子方法,允许子类在特定步骤插入自定义行为。

  • 文档和约定:清晰地文档化模板方法和各个步骤,确保子类正确实现必要的步骤。

7.3. 误区3:子类改变算法的整体结构

问题描述: 开发者可能试图通过子类改变模板方法中算法的整体结构,违反了模板方法模式的设计原则,导致算法的不一致性。

解决方案:

  • 模板方法为final:将模板方法声明为final,防止子类重写和改变算法的整体结构。

  • 明确职责:在抽象类中明确哪些步骤可以被子类重写,哪些步骤必须保持不变。

  • 封装算法骨架:通过封装算法的骨架在抽象类中,确保算法的整体结构不被子类改变。

7.4. 误区4:忽视异常处理

问题描述: 在模板方法中,可能会出现异常情况,如果未能妥善处理,可能导致算法执行中断或系统不稳定。

解决方案:

  • 在模板方法中处理异常:确保模板方法中对可能出现的异常进行捕获和处理,保持算法的稳定性。

  • 在具体步骤中处理异常:子类在实现具体步骤时,妥善处理可能的异常,避免将异常抛给模板方法。

  • 使用钩子方法:通过钩子方法提供异常处理的扩展点,允许子类自定义异常处理逻辑。


8. 模板方法模式的实际应用实例

8.1. 数据处理流程

示例说明

在数据处理系统中,数据的加载、处理和保存等步骤是通用的,但具体的数据处理逻辑可能不同。通过模板方法模式,定义数据处理的通用流程,并在具体类中实现不同的数据处理逻辑。

Java实现示例

// AbstractClass(抽象类)
public abstract class DataProcessor {
    // 模板方法
    public final void process() {
        loadData();
        processData();
        saveData();
    }

    // 通用步骤
    private void loadData() {
        System.out.println("Loading data from source.");
    }

    private void saveData() {
        System.out.println("Saving data to destination.");
    }

    // 需要子类实现的步骤
    protected abstract void processData();
}

// ConcreteClassA(具体类 - CSV数据处理)
public class CsvDataProcessor extends DataProcessor {
    @Override
    protected void processData() {
        System.out.println("Processing CSV data.");
        // 具体的CSV数据处理逻辑
    }
}

// ConcreteClassB(具体类 - JSON数据处理)
public class JsonDataProcessor extends DataProcessor {
    @Override
    protected void processData() {
        System.out.println("Processing JSON data.");
        // 具体的JSON数据处理逻辑
    }
}

// 客户端代码
public class TemplateMethodDataProcessingDemo {
    public static void main(String[] args) {
        DataProcessor csvProcessor = new CsvDataProcessor();
        System.out.println("\nProcessing CSV Data:");
        csvProcessor.process();

        DataProcessor jsonProcessor = new JsonDataProcessor();
        System.out.println("\nProcessing JSON Data:");
        jsonProcessor.process();
    }
}

输出

Processing CSV Data:
Loading data from source.
Processing CSV data.
Saving data to destination.

Processing JSON Data:
Loading data from source.
Processing JSON data.
Saving data to destination.

代码说明

  • DataProcessor类(AbstractClass):定义了数据处理的模板方法 process(),包含数据加载、处理和保存的步骤。

  • CsvDataProcessor类(ConcreteClassA):实现了 processData() 方法,定义了CSV数据的具体处理逻辑。

  • JsonDataProcessor类(ConcreteClassB):实现了 processData() 方法,定义了JSON数据的具体处理逻辑。

  • TemplateMethodDataProcessingDemo类(Client):客户端代码,创建了CSV和JSON数据处理器的实例,并调用 process() 方法执行数据处理流程。

8.2. 测试用例设计

示例说明

在测试框架中,测试用例的执行流程是通用的,包括初始化、执行测试、清理等步骤。但具体的测试内容和验证逻辑可能不同。通过模板方法模式,定义测试用例的执行流程,并在具体类中实现不同的测试内容。

Python实现示例

from abc import ABC, abstractmethod

# AbstractClass(抽象类)
class TestCase(ABC):
    # 模板方法
    def run_test(self):
        self.set_up()
        try:
            self.test()
        except Exception as e:
            self.tear_down()
            print(f"测试失败: {e}")
            return
        self.tear_down()
        print("测试通过。\n")

    # 通用步骤
    def set_up(self):
        print("设置测试环境。")

    def tear_down(self):
        print("清理测试环境。")

    # 需要子类实现的步骤
    @abstractmethod
    def test(self):
        pass

# ConcreteClassA(具体类 - 登录测试)
class LoginTest(TestCase):
    def test(self):
        print("执行登录测试。")
        # 模拟测试逻辑
        assert True  # 假设测试通过

# ConcreteClassB(具体类 - 注册测试)
class RegisterTest(TestCase):
    def test(self):
        print("执行注册测试。")
        # 模拟测试逻辑
        assert False, "用户名已存在"  # 假设测试失败

# 客户端代码
def template_method_testing_demo():
    tests = [LoginTest(), RegisterTest()]
    for test in tests:
        test.run_test()

if __name__ == "__main__":
        template_method_testing_demo()

输出

设置测试环境。
执行登录测试。
清理测试环境。
测试通过。

设置测试环境。
执行注册测试。
清理测试环境。
测试失败: 用户名已存在

代码说明

  • TestCase类(AbstractClass):定义了测试用例的模板方法 run_test(),包括设置环境、执行测试和清理环境的步骤。

  • LoginTest类(ConcreteClassA):实现了 test() 方法,定义了登录测试的具体逻辑。

  • RegisterTest类(ConcreteClassB):实现了 test() 方法,定义了注册测试的具体逻辑。

  • template_method_testing_demo函数(Client):客户端代码,创建了登录和注册测试用例的实例,并调用 run_test() 方法执行测试流程。

8.3. 报告生成系统

示例说明

在报告生成系统中,报告的生成流程是通用的,包括数据收集、数据分析和报告输出等步骤。但不同类型的报告可能有不同的数据分析和输出方式。通过模板方法模式,定义报告生成的通用流程,并在具体类中实现不同的步骤。

Java实现示例

// AbstractClass(抽象类)
public abstract class ReportGenerator {
    // 模板方法
    public final void generateReport() {
        collectData();
        analyzeData();
        formatReport();
        outputReport();
    }

    // 通用步骤
    protected abstract void collectData();
    protected abstract void analyzeData();
    protected abstract void formatReport();

    // 可选步骤(钩子方法)
    protected void outputReport() {
        System.out.println("输出报告。");
    }
}

// ConcreteClassA(具体类 - PDF报告生成)
public class PDFReportGenerator extends ReportGenerator {
    @Override
    protected void collectData() {
        System.out.println("收集PDF报告的数据。");
    }

    @Override
    protected void analyzeData() {
        System.out.println("分析PDF报告的数据。");
    }

    @Override
    protected void formatReport() {
        System.out.println("格式化PDF报告。");
    }

    @Override
    protected void outputReport() {
        System.out.println("生成PDF格式的报告。\n");
    }
}

// ConcreteClassB(具体类 - HTML报告生成)
public class HTMLReportGenerator extends ReportGenerator {
    @Override
    protected void collectData() {
        System.out.println("收集HTML报告的数据。");
    }

    @Override
    protected void analyzeData() {
        System.out.println("分析HTML报告的数据。");
    }

    @Override
    protected void formatReport() {
        System.out.println("格式化HTML报告。");
    }

    @Override
    protected void outputReport() {
        System.out.println("生成HTML格式的报告。\n");
    }
}

// 客户端代码
public class TemplateMethodReportDemo {
    public static void main(String[] args) {
        ReportGenerator pdfReport = new PDFReportGenerator();
        System.out.println("生成PDF报告:");
        pdfReport.generateReport();

        ReportGenerator htmlReport = new HTMLReportGenerator();
        System.out.println("生成HTML报告:");
        htmlReport.generateReport();
    }
}

输出

生成PDF报告:
收集PDF报告的数据。
分析PDF报告的数据。
格式化PDF报告。
生成PDF格式的报告。

生成HTML报告:
收集HTML报告的数据。
分析HTML报告的数据。
格式化HTML报告。
生成HTML格式的报告。

代码说明

  • ReportGenerator类(AbstractClass):定义了报告生成的模板方法 generateReport(),包含数据收集、数据分析、报告格式化和报告输出的步骤。

  • PDFReportGenerator类(ConcreteClassA):实现了 collectData(), analyzeData(), formatReport()outputReport() 方法,定义了生成PDF报告的具体逻辑。

  • HTMLReportGenerator类(ConcreteClassB):实现了 collectData(), analyzeData(), formatReport()outputReport() 方法,定义了生成HTML报告的具体逻辑。

  • TemplateMethodReportDemo类(Client):客户端代码,创建了PDF和HTML报告生成器的实例,并调用 generateReport() 方法执行报告生成流程。


9. 模板方法模式与其他模式的比较

9.1. 模板方法模式 vs. 策略模式

  • 模板方法模式用于定义算法的骨架,将某些步骤延迟到子类中,确保算法的结构不被改变。

  • 策略模式用于定义一系列算法,将每个算法封装起来,使它们可以互换,算法的选择由客户端决定。

关键区别:

  • 控制权不同

    • 模板方法模式:算法的控制权在抽象类中,子类仅实现具体的步骤。

    • 策略模式:算法的控制权在客户端,客户端决定使用哪种策略。

  • 适用场景不同

    • 模板方法模式:适用于需要控制算法的整体流程,并允许子类定制部分步骤的场景。

    • 策略模式:适用于需要在运行时动态选择算法,并让算法独立于使用它的客户端的场景。

  • 继承与组合

    • 模板方法模式:基于继承,子类继承抽象类并重写特定方法。

    • 策略模式:基于组合,上下文类持有策略接口的引用,通过组合实现算法的切换。

9.2. 模板方法模式 vs. 观察者模式

  • 模板方法模式用于定义算法的骨架,将某些步骤延迟到子类中。

  • 观察者模式用于建立一对多的通信机制,当一个对象的状态变化时,所有依赖于它的对象都会得到通知并自动更新。

关键区别:

  • 目的不同

    • 模板方法模式:关注算法的定义和复用。

    • 观察者模式:关注对象间的通信和状态同步。

  • 结构不同

    • 模板方法模式:涉及抽象类和子类的继承关系。

    • 观察者模式:涉及被观察者和观察者的注册与通知机制。

  • 应用场景不同

    • 模板方法模式:适用于需要控制算法流程,并允许子类定制部分步骤的场景。

    • 观察者模式:适用于需要对象间的通知和同步更新的场景。

9.3. 模板方法模式 vs. 责任链模式

  • 模板方法模式用于定义算法的骨架,将某些步骤延迟到子类中。

  • 责任链模式用于将请求沿着一条链传递,直到有一个对象处理请求。

关键区别:

  • 目的不同

    • 模板方法模式:关注算法的结构和步骤的复用。

    • 责任链模式:关注请求的传递和处理流程的灵活性。

  • 结构不同

    • 模板方法模式:通过继承关系定义算法的骨架。

    • 责任链模式:通过链式结构的对象传递请求。

  • 应用场景不同

    • 模板方法模式:适用于需要定义算法流程,并允许子类定制部分步骤的场景。

    • 责任链模式:适用于需要多个对象有机会处理请求,并动态地决定请求处理者的场景。

9.4. 模板方法模式 vs. 装饰者模式

  • 模板方法模式用于定义算法的骨架,将某些步骤延迟到子类中。

  • 装饰者模式用于动态地为对象添加新功能,通过装饰者对象包装原有对象,增强其功能。

关键区别:

  • 目的不同

    • 模板方法模式:关注算法的定义和复用。

    • 装饰者模式:关注对象功能的动态扩展和增强。

  • 结构不同

    • 模板方法模式:通过继承关系定义算法的骨架。

    • 装饰者模式:通过组合关系装饰对象,增加功能。

  • 应用场景不同

    • 模板方法模式:适用于需要定义算法流程,并允许子类定制部分步骤的场景。

    • 装饰者模式:适用于需要在不修改对象的基础上动态增加功能的场景。


10. 模板方法模式的扩展与变体

模板方法模式在实际应用中可以根据需求进行一些扩展和变体,以适应不同的场景和需求。

10.1. 钩子方法(Hook Methods)

钩子方法是模板方法模式中的一种变体,允许子类在不改变模板方法的结构的情况下,插入自定义的行为。

实现方式:

  • 定义钩子方法:在抽象类中定义空的钩子方法,子类可以选择性地重写这些方法。

  • 调用钩子方法:在模板方法中调用钩子方法,允许子类在特定步骤中插入自定义行为。

示例:

// AbstractClass(抽象类)
public abstract class BeverageWithHook {
    // 模板方法
    public final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) { // 钩子方法
            addCondiments();
        }
    }

    private void boilWater() {
        System.out.println("Boiling water.");
    }

    private void pourInCup() {
        System.out.println("Pouring into cup.");
    }

    protected abstract void brew();
    protected abstract void addCondiments();

    // 钩子方法
    protected boolean customerWantsCondiments() {
        return true; // 默认情况
    }
}

// ConcreteClassA(具体类 - 咖啡)
public class CoffeeWithHook extends BeverageWithHook {
    @Override
    protected void brew() {
        System.out.println("Dripping coffee through filter.");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding sugar and milk.");
    }

    @Override
    protected boolean customerWantsCondiments() {
        return false; // 客户不需要添加调料
    }
}

// 客户端代码
public class TemplateMethodHookDemo {
    public static void main(String[] args) {
        BeverageWithHook coffee = new CoffeeWithHook();
        coffee.prepareRecipe();
    }
}

输出

Boiling water.
Dripping coffee through filter.
Pouring into cup.

说明:

  • customerWantsCondiments() 是一个钩子方法,允许子类决定是否执行添加调料的步骤。

  • CoffeeWithHook 类中,重写了 customerWantsCondiments() 方法,返回 false,因此不会执行 addCondiments()

10.2. 模板方法与策略模式结合

在某些复杂的场景中,可以将模板方法模式与策略模式结合使用,实现更加灵活和可扩展的设计。

实现方式:

  • 在模板方法中使用策略:将某些可变的步骤通过策略模式来实现,允许子类通过组合策略对象来定制行为。

  • 动态切换策略:在模板方法的执行过程中,根据需要动态切换策略对象,实现更灵活的算法变换。

示例:

// Strategy接口
public interface BrewStrategy {
    void brew();
}

// ConcreteStrategyA类(滤泡式冲泡)
public class FilterBrewStrategy implements BrewStrategy {
    @Override
    public void brew() {
        System.out.println("Dripping coffee through filter.");
    }
}

// ConcreteStrategyB类(浸泡式冲泡)
public class ImmersionBrewStrategy implements BrewStrategy {
    @Override
    public void brew() {
        System.out.println("Steeping coffee in hot water.");
    }
}

// AbstractClass(抽象类)
public abstract class BeverageWithStrategy {
    private BrewStrategy brewStrategy;

    // 模板方法
    public final void prepareRecipe() {
        boilWater();
        brewStrategy.brew();
        pourInCup();
        addCondiments();
    }

    protected void setBrewStrategy(BrewStrategy brewStrategy) {
        this.brewStrategy = brewStrategy;
    }

    private void boilWater() {
        System.out.println("Boiling water.");
    }

    private void pourInCup() {
        System.out.println("Pouring into cup.");
    }

    protected abstract void addCondiments();
}

// ConcreteClassA(具体类 - 咖啡)
public class CoffeeWithStrategy extends BeverageWithStrategy {
    public CoffeeWithStrategy() {
        setBrewStrategy(new FilterBrewStrategy()); // 使用滤泡式冲泡
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding sugar and milk.");
    }
}

// ConcreteClassB(具体类 - 冰咖啡)
public class IcedCoffee extends BeverageWithStrategy {
    public IcedCoffee() {
        setBrewStrategy(new ImmersionBrewStrategy()); // 使用浸泡式冲泡
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding ice and sugar.");
    }
}

// 客户端代码
public class TemplateMethodStrategyDemo {
    public static void main(String[] args) {
        BeverageWithStrategy coffee = new CoffeeWithStrategy();
        System.out.println("\nMaking Coffee:");
        coffee.prepareRecipe();

        BeverageWithStrategy icedCoffee = new IcedCoffee();
        System.out.println("\nMaking Iced Coffee:");
        icedCoffee.prepareRecipe();
    }
}

输出

Making Coffee:
Boiling water.
Dripping coffee through filter.
Pouring into cup.
Adding sugar and milk.

Making Iced Coffee:
Boiling water.
Steeping coffee in hot water.
Pouring into cup.
Adding ice and sugar.

说明:

  • BrewStrategy接口 定义了冲泡策略。

  • FilterBrewStrategyImmersionBrewStrategy 实现了不同的冲泡算法。

  • BeverageWithStrategy类(AbstractClass):通过组合策略对象,实现了冲泡算法的动态切换。

  • CoffeeWithStrategyIcedCoffee 类分别使用不同的冲泡策略,实现了不同类型的饮料冲泡流程。


11. 总结

模板方法模式(Template Method Pattern) 通过在抽象类中定义算法的骨架,将某些步骤延迟到子类中,实现了算法结构的复用和行为的定制。该模式适用于需要控制算法流程、允许子类定制特定步骤、以及希望减少代码重复的场景。通过模板方法模式,系统设计更加清晰和模块化,提升了代码的可维护性和可扩展性。

关键学习点回顾:

  1. 理解模板方法模式的核心概念:在抽象类中定义算法的骨架,将某些步骤延迟到子类中,实现算法的复用和行为的定制。

  2. 掌握模板方法模式的结构:包括抽象类、具体类、模板方法以及需要子类实现的步骤。

  3. 识别适用的应用场景:需要控制算法流程、允许子类定制特定步骤、避免大量条件判断等。

  4. 认识模板方法模式的优缺点:代码复用、控制算法结构、灵活性高;但可能增加类的数量、设计复杂性高、子类依赖性强等。

  5. 理解常见误区及解决方案:避免过度使用、确保算法的完整性、控制子类对算法结构的影响、妥善处理异常等。

  6. 实际应用中的模板方法模式实例:饮料冲泡系统、数据处理流程、测试用例设计、报告生成系统等。

  7. 模板方法模式的扩展与变体:钩子方法、与策略模式结合等。


12. 参考资料