1. 模版方法模式概述
模版方法模式是《Head First设计模式》中介绍的一种行为型设计模式,它定义了一个操作中的算法骨架,而将某些步骤延迟到子类中实现。这种模式允许子类在不改变算法结构的情况下重新定义算法的某些特定步骤。
在C++中实现模版方法模式时,通常会使用虚函数来实现那些需要子类重写的步骤。这种模式特别适用于以下场景:
- 当多个类有相似的行为,但具体实现细节不同时
- 当需要控制子类扩展的粒度时
- 当存在一个固定流程,但某些步骤需要灵活变化时
提示:模版方法模式与策略模式不同,前者通过继承改变部分行为,后者通过组合改变整个算法。
2. 模式结构与C++实现
2.1 基本结构解析
模版方法模式包含两个主要部分:
- 抽象基类:定义算法骨架和基本操作
- 具体子类:实现特定的操作步骤
典型的C++实现如下:
cpp复制class AbstractClass {
public:
// 模版方法,定义算法骨架
void TemplateMethod() {
PrimitiveOperation1();
PrimitiveOperation2();
}
virtual ~AbstractClass() = default;
protected:
// 基本操作,子类需要实现
virtual void PrimitiveOperation1() = 0;
virtual void PrimitiveOperation2() = 0;
};
class ConcreteClass : public AbstractClass {
protected:
void PrimitiveOperation1() override {
// 具体实现
}
void PrimitiveOperation2() override {
// 具体实现
}
};
2.2 C++实现细节
在C++中实现模版方法模式时,有几个关键点需要注意:
-
虚函数的使用:
- 纯虚函数(=0)强制子类实现特定操作
- 普通虚函数允许子类选择性地重写
- 非虚函数固定算法步骤,防止子类修改
-
访问控制:
- 模版方法通常设为public
- 基本操作设为protected,限制外部直接调用
- 析构函数应为虚函数,确保正确释放资源
-
钩子方法(Hook):
- 提供默认实现的虚函数
- 子类可选择重写以影响算法流程
cpp复制class WithHook {
public:
void TemplateMethod() {
PrimitiveOperation1();
if (Hook()) {
PrimitiveOperation2();
}
}
protected:
virtual bool Hook() { return true; } // 钩子方法
virtual void PrimitiveOperation1() = 0;
virtual void PrimitiveOperation2() = 0;
};
3. 实际应用案例分析
3.1 文档处理系统示例
考虑一个文档处理系统,处理流程固定(打开、读取、处理、保存),但具体实现因文档类型而异:
cpp复制class DocumentProcessor {
public:
void ProcessDocument() {
OpenDocument();
ReadData();
ProcessContent();
SaveDocument();
}
virtual ~DocumentProcessor() = default;
protected:
virtual void OpenDocument() = 0;
virtual void ReadData() = 0;
virtual void ProcessContent() {
// 默认处理逻辑
}
virtual void SaveDocument() = 0;
};
class PdfProcessor : public DocumentProcessor {
protected:
void OpenDocument() override {
// PDF特定打开逻辑
}
void ReadData() override {
// PDF特定读取逻辑
}
void SaveDocument() override {
// PDF特定保存逻辑
}
};
3.2 游戏开发中的应用
在游戏开发中,模版方法模式可用于定义游戏角色的行为流程:
cpp复制class GameAI {
public:
void Turn() {
CollectPerception();
Think();
Act();
}
protected:
virtual void CollectPerception() = 0;
virtual void Think() = 0;
virtual void Act() = 0;
// 钩子方法
virtual bool ShouldRest() { return false; }
};
class MonsterAI : public GameAI {
protected:
void CollectPerception() override {
// 怪物感知逻辑
}
void Think() override {
// 怪物思考逻辑
}
void Act() override {
// 怪物行动逻辑
}
bool ShouldRest() override {
// 怪物特定休息条件
return true;
}
};
4. 模式优缺点与适用场景
4.1 优势分析
- 代码复用:将不变行为移到超类,避免代码重复
- 扩展控制:通过钩子方法控制子类扩展点
- 流程标准化:确保算法结构的一致性
- 好莱坞原则:"不要调用我们,我们会调用你",子类只需关注特定步骤
4.2 局限性
- 继承的固有问题:C++中多重继承可能带来复杂性
- 灵活性受限:算法骨架一旦确定难以修改
- 可能导致类膨胀:每个变体都需要一个子类
4.3 适用场景判断
模版方法模式特别适合以下情况:
- 多个类共享相同算法,但部分步骤不同
- 需要控制子类扩展的粒度
- 存在固定流程,但某些步骤需要灵活变化
- 框架设计,允许用户自定义特定行为
5. 常见问题与解决方案
5.1 虚函数与性能
在性能敏感的场景中,虚函数调用可能带来开销。可以考虑以下优化:
- 将频繁调用的模版方法声明为final
- 使用CRTP(奇异递归模板模式)实现静态多态:
cpp复制template <typename T>
class Base {
public:
void TemplateMethod() {
static_cast<T*>(this)->PrimitiveOperation1();
static_cast<T*>(this)->PrimitiveOperation2();
}
};
class Derived : public Base<Derived> {
public:
void PrimitiveOperation1() { /*...*/ }
void PrimitiveOperation2() { /*...*/ }
};
5.2 设计注意事项
- 尽量减少基本操作的数量,避免子类负担过重
- 命名要清晰区分模版方法和基本操作
- 考虑将模版方法声明为non-virtual,防止子类意外重写
- 合理使用钩子方法,提供灵活扩展点
5.3 与其他模式的关系
-
与策略模式:
- 模版方法使用继承改变部分行为
- 策略模式使用组合改变整个算法
-
与工厂方法模式:
- 工厂方法常作为模版方法的一个步骤
-
与观察者模式:
- 模版方法可定义通知观察者的固定流程
6. C++11/14/17特性增强
现代C++提供了更多实现模版方法模式的选择:
- 使用final关键字防止模版方法被重写:
cpp复制class AbstractClass {
public:
void TemplateMethod() final {
// ...
}
};
- 使用override明确表示重写:
cpp复制class ConcreteClass : public AbstractClass {
protected:
void PrimitiveOperation1() override {
// ...
}
};
- 使用constexpr实现编译期模版方法:
cpp复制template <typename T>
class Processor {
public:
constexpr void Process() {
T::Step1();
T::Step2();
}
};
7. 测试与调试技巧
7.1 单元测试策略
-
测试抽象基类:
- 创建测试专用的具体子类
- 验证模版方法的调用顺序
-
测试具体子类:
- 验证每个基本操作的实现
- 测试钩子方法的影响
-
使用GMock模拟测试:
cpp复制class MockConcrete : public AbstractClass {
public:
MOCK_METHOD(void, PrimitiveOperation1, (), (override));
MOCK_METHOD(void, PrimitiveOperation2, (), (override));
};
TEST(TemplateMethodTest, ExecutionOrder) {
MockConcrete mock;
EXPECT_CALL(mock, PrimitiveOperation1()).Times(1);
EXPECT_CALL(mock, PrimitiveOperation2()).Times(1);
mock.TemplateMethod();
}
7.2 调试技巧
- 在模版方法中添加日志输出:
cpp复制void TemplateMethod() {
std::cout << "Starting PrimitiveOperation1" << std::endl;
PrimitiveOperation1();
std::cout << "Completed PrimitiveOperation1" << std::endl;
}
-
使用断点调试:
- 在模版方法入口设置断点
- 跟踪基本操作的调用顺序
-
运行时类型检查:
cpp复制void TemplateMethod() {
if (typeid(*this) == typeid(ConcreteClass)) {
// 特定处理
}
}
8. 性能考量与优化
8.1 虚函数开销分析
虚函数调用通常比普通函数调用慢,因为:
- 需要通过虚函数表(vtable)间接调用
- 阻止了内联优化
在性能关键路径上,可以考虑:
- 将频繁调用的模版方法声明为final
- 使用静态多态(CRTP)
- 将基本操作实现为非虚函数,通过策略对象注入
8.2 缓存友好性优化
- 将相关数据放在连续内存中
- 减少虚函数调用层次
- 使用数据导向设计:
cpp复制class DataOrientedProcessor {
public:
template <typename T>
void ProcessBatch(T* items, size_t count) {
for (size_t i = 0; i < count; ++i) {
Step1(items[i]);
Step2(items[i]);
}
}
private:
void Step1(ItemType& item) { /*...*/ }
void Step2(ItemType& item) { /*...*/ }
};
9. 现代C++最佳实践
9.1 使用智能指针管理资源
cpp复制class AbstractProcessor {
public:
virtual ~AbstractProcessor() = default;
void Process() {
Setup();
Execute();
Cleanup();
}
protected:
virtual void Setup() = 0;
virtual void Execute() = 0;
virtual void Cleanup() = 0;
};
class SafeProcessor : public AbstractProcessor {
protected:
void Setup() override {
resource_ = std::make_unique<Resource>();
}
void Cleanup() override {
resource_.reset();
}
private:
std::unique_ptr<Resource> resource_;
};
9.2 使用lambda实现策略
cpp复制class TemplateWithLambda {
public:
using Operation = std::function<void()>;
void Execute(Operation op1, Operation op2) {
op1();
op2();
}
};
// 使用
TemplateWithLambda templ;
templ.Execute(
[] { /* 操作1 */ },
[] { /* 操作2 */ }
);
9.3 使用变参模板
cpp复制template <typename... Steps>
class VariadicTemplate {
public:
void Execute() {
(Steps::Execute(), ...);
}
};
struct Step1 { static void Execute() { /*...*/ } };
struct Step2 { static void Execute() { /*...*/ } };
// 使用
VariadicTemplate<Step1, Step2> processor;
processor.Execute();
10. 设计模式组合应用
10.1 与工厂方法模式结合
cpp复制class Application {
public:
void Run() {
auto doc = CreateDocument();
doc->Open();
doc->Process();
doc->Save();
}
protected:
virtual std::unique_ptr<Document> CreateDocument() = 0;
};
10.2 与观察者模式结合
cpp复制class ObservableProcessor : public AbstractProcessor {
public:
void AddObserver(Observer* o) {
observers_.push_back(o);
}
void Process() override {
NotifyObservers(ProcessStart);
AbstractProcessor::Process();
NotifyObservers(ProcessEnd);
}
private:
std::vector<Observer*> observers_;
};
10.3 与策略模式结合
cpp复制class HybridTemplate {
public:
void SetStep1Strategy(std::function<void()> strat) {
step1_ = strat;
}
void Execute() {
step1_();
FixedStep2();
}
private:
std::function<void()> step1_;
void FixedStep2() { /*...*/ }
};
在实际项目中,模版方法模式很少单独使用,通常与其他模式组合以发挥更大威力。理解各种模式的适用场景和相互关系,才能做出最合适的设计决策。