模板方法模式是面向对象软件设计模式之一,其主要意图是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下重新定义算法的某些特定步骤。
动机
在软件开发中,常常会遇到这样的情况:一个任务可以被分解为多个步骤来完成,其中有些步骤的实现方法是对所有子类通用的,而有些步骤则需要针对不同的子类有不同的实现方式。模板方法模式正是解决这一问题的有效手段。通过使用模板方法模式,可以将不变的部分代码抽离出来放在基类中,而将可变的部分留给子类去实现。这样不仅减少了代码的重复,而且还能够保证变与不变的部分之间的联系。
意图
模板方法模式的主要意图是定义一个操作中的算法的骨架,而将一些步骤的实现延迟到子类中。这样做可以让子类在不改变算法整体结构的前提下去重写算法的某些特定部分。
适用场合
- 不变的算法骨架:当某个抽象类有多个子类,但是它们有共同的算法步骤,只是某些步骤的具体实现不同,可以通过模版方法模式将这些不变的部分提取到抽象类中,子类只需要实现特定的步骤。
- 避免代码重复:多个类中若有一段代码完全相同或相似,可以考虑使用模版方法模式来抽取共享的部分,减少代码重复。
- 控制子类扩展:模版方法模式可以通过在模版方法中调用"钩子"方法,来控制子类在生命周期中的特定点上能够做什么,不能够做什么。钩子方法在抽象类中通常是空实现,子类可以覆盖这些方法来实现特定功能。
示例
模板方法模式的一个经典示例是咖啡店和茶馆的饮料制作过程。制作咖啡和茶的基本流程大致相同(准备热水、冲泡、倒入杯子、加调料),但具体的步骤(如冲泡的方式、加何种调料)则不同。通过定义一个模版方法来实现这个流程,基类可以控制整个流程的执行顺序,而具体的冲泡和加调料动作则由子类实现。
模板方法模式是一种非常有用的设计模式,在很多框架和库中都有应用。正确地使用模版方法模式可以使代码更加清晰、易于扩展和维护。
面是一个简单的C++示例,展示了如何使用模板方法模式来实现咖啡和茶的制作过程。在这个例子中,基类 Beverage
定义了一个模板方法 prepareRecipe()
,该方法包含了制作饮料的通用步骤,而具体的步骤(如冲泡和添加调料)则由子类实现。
代码示例
cpp
#include <iostream>
using namespace std;
// 基类:Beverage
class Beverage {
public:
// 模板方法,定义了制作饮料的算法骨架
void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
// 子类不需要覆盖的方法,因为这些步骤对所有饮料都是相同的
void boilWater() {
cout << "Boiling water" << endl;
}
void pourInCup() {
cout << "Pouring into cup" << endl;
}
// 子类必须覆盖的方法,因为这些步骤对不同饮料是不同的
virtual void brew() = 0; // 纯虚函数,必须在子类中实现
virtual void addCondiments() = 0; // 纯虚函数,必须在子类中实现
};
// 子类:Coffee
class Coffee : public Beverage {
public:
void brew() override {
cout << "Dripping Coffee through filter" << endl;
}
void addCondiments() override {
cout << "Adding Sugar and Milk" << endl;
}
};
// 子类:Tea
class Tea : public Beverage {
public:
void brew() override {
cout << "Steeping the tea" << endl;
}
void addCondiments() override {
cout << "Adding Lemon" << endl;
}
};
int main() {
cout << "Making Coffee..." << endl;
Coffee coffee;
coffee.prepareRecipe();
cout << "\nMaking Tea..." << endl;
Tea tea;
tea.prepareRecipe();
return 0;
}
运行结果
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
:prepareRecipe()
是模板方法,定义了制作饮料的步骤:煮水、冲泡、倒入杯子、加调料。boilWater()
和pourInCup()
是所有饮料共有的步骤,因此在基类中实现。brew()
和addCondiments()
是纯虚函数,需要在子类中实现,因为这些步骤对不同的饮料有不同的实现。
-
子类
Coffee
和Tea
:Coffee
子类实现了brew()
和addCondiments()
,分别表示冲泡咖啡和添加糖和牛奶。Tea
子类实现了brew()
和addCondiments()
,分别表示泡茶和添加柠檬。
-
main
函数:- 创建
Coffee
和Tea
对象,并调用它们的prepareRecipe()
方法,输出了制作咖啡和茶的过程。
- 创建
适用场合
- 通用流程,具体步骤不同:当多个子类需要遵循相同的流程,但具体的步骤实现不同,可以使用模板方法模式。例如,不同的支付方式(信用卡、支付宝、微信)可以遵循相同的支付流程,但具体的支付接口和验证方式不同。
- 避免重复代码:在多个子类中存在相同的逻辑时,可以将这些逻辑提取到基类的模板方法中,避免代码重复。
模板方法模式通过将算法的结构固定,允许子类灵活地实现具体步骤,提供了一种优雅的方式来处理类似的任务。
模板方法模式经常与其他设计模式协同使用,以解决更复杂的设计问题。常见的协同模式包括策略模式、工厂方法模式和状态模式等。下面我们将重点介绍模板方法模式与策略模式的协同使用,并给出一个C++代码示例。
模板方法模式与策略模式的协同使用
动机
模板方法模式用于定义一个算法的骨架,而将某些步骤的实现延迟到子类中。策略模式用于定义一系列可互换的算法,并将这些算法封装在独立的类中。通过将策略模式与模板方法模式结合,可以在一个模板方法中使用不同的策略,从而提供更大的灵活性。
适用场景
- 算法的某些步骤需要根据不同的条件动态选择不同的实现:可以通过策略模式将这些步骤的实现封装在不同的策略类中,然后在模板方法中根据需要选择合适的策略。
- 需要在运行时动态改变算法的某些步骤:策略模式允许在运行时动态更换算法,而模板方法模式确保了算法的整体结构不变。
代码示例
假设我们有一个任务是处理数据,不同的处理策略可以根据不同的需求进行选择。我们使用模板方法模式来定义数据处理的骨架,使用策略模式来实现不同的处理策略。
1. 定义策略接口和具体策略
cpp
// 策略接口
class DataProcessingStrategy {
public:
virtual void process() = 0;
virtual ~DataProcessingStrategy() = default;
};
// 具体策略1:压缩数据
class CompressStrategy : public DataProcessingStrategy {
public:
void process() override {
cout << "Compressing data" << endl;
}
};
// 具体策略2:加密数据
class EncryptStrategy : public DataProcessingStrategy {
public:
void process() override {
cout << "Encrypting data" << endl;
}
};
// 具体策略3:校验数据
class VerifyStrategy : public DataProcessingStrategy {
public:
void process() override {
cout << "Verifying data" << endl;
}
};
2. 定义抽象类和模板方法
cpp
// 抽象类
class DataProcessor {
protected:
DataProcessingStrategy* strategy;
public:
DataProcessor(DataProcessingStrategy* s) : strategy(s) {}
virtual ~DataProcessor() {
delete strategy;
}
// 模板方法
void processData() {
load();
strategy->process();
save();
}
virtual void load() {
cout << "Loading data" << endl;
}
virtual void save() {
cout << "Saving data" << endl;
}
};
3. 定义具体的数据处理器
cpp
// 具体的数据处理器1:使用压缩策略
class CompressDataProcessor : public DataProcessor {
public:
CompressDataProcessor() : DataProcessor(new CompressStrategy()) {}
};
// 具体的数据处理器2:使用加密策略
class EncryptDataProcessor : public DataProcessor {
public:
EncryptDataProcessor() : DataProcessor(new EncryptStrategy()) {}
};
// 具体的数据处理器3:使用校验策略
class VerifyDataProcessor : public DataProcessor {
public:
VerifyDataProcessor() : DataProcessor(new VerifyStrategy()) {}
};
4. 客户端代码
cpp
#include <iostream>
using namespace std;
int main() {
// 使用压缩策略处理数据
cout << "Using Compress Strategy..." << endl;
DataProcessor* processor1 = new CompressDataProcessor();
processor1->processData();
delete processor1;
// 使用加密策略处理数据
cout << "\nUsing Encrypt Strategy..." << endl;
DataProcessor* processor2 = new EncryptDataProcessor();
processor2->processData();
delete processor2;
// 使用校验策略处理数据
cout << "\nUsing Verify Strategy..." << endl;
DataProcessor* processor3 = new VerifyDataProcessor();
processor3->processData();
delete processor3;
return 0;
}
运行结果
Using Compress Strategy...
Loading data
Compressing data
Saving data
Using Encrypt Strategy...
Loading data
Encrypting data
Saving data
Using Verify Strategy...
Loading data
Verifying data
Saving data
解释
-
策略接口
DataProcessingStrategy
:- 定义了一个纯虚函数
process()
,用于实现数据处理的具体策略。 - 具体策略
CompressStrategy
、EncryptStrategy
和VerifyStrategy
分别实现了数据压缩、加密和校验的策略。
- 定义了一个纯虚函数
-
抽象类
DataProcessor
:- 包含一个策略对象
strategy
,并通过构造函数传递具体的策略对象。 - 定义了模板方法
processData()
,该方法调用了load()
、strategy->process()
和save()
,确保数据处理的流程一致。 load()
和save()
是通用的数据加载和保存步骤,可以在子类中根据需要进行扩展。
- 包含一个策略对象
-
具体数据处理器类:
CompressDataProcessor
、EncryptDataProcessor
和VerifyDataProcessor
分别使用不同的策略对象初始化DataProcessor
。
-
客户端代码:
- 创建不同的数据处理器对象,并调用
processData()
方法来处理数据,输出了不同的处理策略的结果。
- 创建不同的数据处理器对象,并调用
通过这种方式,模板方法模式和策略模式的结合提供了更大的灵活性,允许在运行时动态选择不同的数据处理策略,同时保持数据处理流程的一致性。