概念
模板方法模式是一种行为设计模式,它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。模板方法使得子类可以不改变算法结构的情况下,重新定义算法中的某些特定步骤。
结构
模板方法模式包含以下主要角色:
-
抽象类(Abstract Class):定义抽象的原语操作(primitive operations),具体的子类将重定义它们以实现算法的特定步骤。实现一个模板方法,定义算法的骨架。
-
具体类(Concrete Class):实现原语操作以完成算法中与特定子类相关的步骤。
实现示例
#include <iostream>
// 抽象类
class AbstractClass {
public:
// 模板方法,定义算法骨架
void TemplateMethod() {
PrimitiveOperation1();
PrimitiveOperation2();
}
// 基本操作1 - 抽象方法,由子类实现
virtual void PrimitiveOperation1() = 0;
// 基本操作2 - 抽象方法,由子类实现
virtual void PrimitiveOperation2() = 0;
virtual ~AbstractClass() = default;
};
// 具体类A
class ConcreteClassA : public AbstractClass {
public:
void PrimitiveOperation1() override {
std::cout << "ConcreteClassA: PrimitiveOperation1 implementation\n";
}
void PrimitiveOperation2() override {
std::cout << "ConcreteClassA: PrimitiveOperation2 implementation\n";
}
};
// 具体类B
class ConcreteClassB : public AbstractClass {
public:
void PrimitiveOperation1() override {
std::cout << "ConcreteClassB: PrimitiveOperation1 implementation\n";
}
void PrimitiveOperation2() override {
std::cout << "ConcreteClassB: PrimitiveOperation2 implementation\n";
}
};
int main() {
AbstractClass* a = new ConcreteClassA();
a->TemplateMethod();
AbstractClass* b = new ConcreteClassB();
b->TemplateMethod();
delete a;
delete b;
return 0;
}
输出结果
ConcreteClassA: PrimitiveOperation1 implementation
ConcreteClassA: PrimitiveOperation2 implementation
ConcreteClassB: PrimitiveOperation1 implementation
ConcreteClassB: PrimitiveOperation2 implementation
模式特点
-
代码复用:模板方法模式是一种代码复用的基本技术,将公共行为提取到父类中。
-
反向控制:父类调用子类的操作,而不是子类调用父类的操作(好莱坞原则:"Don't call us, we'll call you")。
-
灵活性:子类可以通过重写方法来实现特定的行为,而不需要改变算法结构。
应用场景
-
当多个类有相同的方法,且逻辑基本相同,只有一些细节不同时。
-
当需要控制子类扩展时,模板方法只在特定点调用"hook"操作,这样就只允许在这些点进行扩展。
-
当需要重构代码,将公共行为提取到父类中时。
优缺点
优点:
-
封装不变部分,扩展可变部分
-
提取公共代码,便于维护
-
行为由父类控制,子类实现
缺点:
-
每一个不同的实现都需要一个子类,导致类的个数增加,系统更加庞大
实际应用示例:游戏框架
#include <iostream>
// 游戏抽象类
class Game {
public:
// 模板方法
void Play() {
Initialize();
StartPlay();
EndPlay();
}
virtual ~Game() = default;
protected:
virtual void Initialize() = 0;
virtual void StartPlay() = 0;
virtual void EndPlay() = 0;
};
// 足球游戏
class Football : public Game {
protected:
void Initialize() override {
std::cout << "Football Game Initialized! Start playing.\n";
}
void StartPlay() override {
std::cout << "Football Game Started. Enjoy the game!\n";
}
void EndPlay() override {
std::cout << "Football Game Finished!\n";
}
};
// 篮球游戏
class Basketball : public Game {
protected:
void Initialize() override {
std::cout << "Basketball Game Initialized! Start playing.\n";
}
void StartPlay() override {
std::cout << "Basketball Game Started. Enjoy the game!\n";
}
void EndPlay() override {
std::cout << "Basketball Game Finished!\n";
}
};
int main() {
Game* game = new Football();
game->Play();
delete game;
game = new Basketball();
game->Play();
delete game;
return 0;
}
通俗化总结
通俗理解:做菜的固定流程
想象你妈妈教你做一道菜,比如红烧肉,她告诉你固定的步骤框架:
-
准备食材(五花肉、调料等)
-
焯水去腥
-
炒糖色
-
炖煮收汁
这就是一个"模板方法"——固定的做菜流程。但具体到每一步:
-
准备食材:你可以选择不同的五花肉部位
-
焯水时间:根据肉块大小调整
-
炒糖色:可以用白糖或冰糖,火候自己掌握
-
炖煮时间:根据个人口味偏好调整
妈妈给了你做菜的"模板"(固定流程),但允许你在某些步骤上自由发挥(具体实现)。
其他生活例子
-
咖啡/茶制作:
-
模板方法:①烧水 ②加原料 ③冲泡 ④加调料
-
具体实现:咖啡用咖啡粉,茶用茶叶;咖啡加糖奶,茶可能加柠檬
-
-
银行办业务:
-
模板流程:①取号 ②等待 ③办理 ④评价
-
不同业务(存款/开户/转账)在第③步具体实现不同
-
-
组装电脑:
-
固定流程:①装CPU ②装内存 ③装硬盘 ④接线
-
具体实现:可以选择不同品牌型号的配件
-
模式精髓总结
固定骨架:定义不变的流程步骤(比如做菜必须按顺序来,不能先炖煮再焯水)
灵活细节:允许子类自定义某些步骤的具体实现(比如糖色用冰糖还是白糖)
控制权在父类:就像妈妈控制做菜流程,你只需要负责具体操作
这种模式特别适用于:
-
有固定流程但细节可变的情况
-
希望避免重复代码(多个子类共享相同流程)
-
需要控制子类扩展点的情况
就像各种菜系都有"先炒后炖"的固定套路,但具体炒什么、炖什么可以千变万化!
实际C++项目开发中的示例
1. 文档导出框架示例
例如需要开发一个支持多种格式的文档导出功能(PDF、HTML、Markdown)
#include <iostream>
#include <string>
#include <vector>
// 抽象文档导出类
class DocumentExporter {
public:
// 模板方法
void ExportDocument(const std::string& title, const std::vector<std::string>& content) {
CreateHeader(title);
for (const auto& paragraph : content) {
AddParagraph(paragraph);
}
CreateFooter();
Finalize();
}
virtual ~DocumentExporter() = default;
protected:
virtual void CreateHeader(const std::string& title) = 0;
virtual void AddParagraph(const std::string& text) = 0;
virtual void CreateFooter() = 0;
virtual void Finalize() {
// 默认实现,子类可重写
std::cout << "Export completed!\n";
}
};
// PDF导出实现
class PDFExporter : public DocumentExporter {
protected:
void CreateHeader(const std::string& title) override {
std::cout << "Creating PDF header with title: " << title << "\n";
}
void AddParagraph(const std::string& text) override {
std::cout << "Adding PDF paragraph: " << text << "\n";
}
void CreateFooter() override {
std::cout << "Adding PDF footer with page number\n";
}
void Finalize() override {
std::cout << "Generating PDF file...\n";
DocumentExporter::Finalize();
}
};
// HTML导出实现
class HTMLExporter : public DocumentExporter {
protected:
void CreateHeader(const std::string& title) override {
std::cout << "<html><head><title>" << title << "</title></head><body>\n";
}
void AddParagraph(const std::string& text) override {
std::cout << "<p>" << text << "</p>\n";
}
void CreateFooter() override {
std::cout << "</body></html>\n";
}
};
// 使用示例
int main() {
std::vector<std::string> content = {
"This is the first paragraph.",
"This is the second paragraph.",
"Here goes the third one."
};
DocumentExporter* pdfExporter = new PDFExporter();
pdfExporter->ExportDocument("Sample Document", content);
delete pdfExporter;
std::cout << "\n";
DocumentExporter* htmlExporter = new HTMLExporter();
htmlExporter->ExportDocument("Sample Document", content);
delete htmlExporter;
return 0;
}
2. 游戏引擎中的场景加载系统
#include <iostream>
#include <memory>
// 游戏场景加载基类
class SceneLoader {
public:
// 模板方法
void LoadScene() {
PreloadResources();
LoadMainAssets();
InitializeSceneObjects();
PostInitialization();
if (EnableDebug()) {
AttachDebugTools();
}
}
virtual ~SceneLoader() = default;
protected:
virtual void PreloadResources() = 0;
virtual void LoadMainAssets() = 0;
virtual void InitializeSceneObjects() = 0;
// 钩子方法(Hook) - 可选步骤
virtual void PostInitialization() {
// 默认空实现
}
virtual bool EnableDebug() const {
return false; // 默认不启用调试
}
virtual void AttachDebugTools() {
std::cout << "Attaching default debug tools...\n";
}
};
// 主菜单场景加载
class MainMenuLoader : public SceneLoader {
protected:
void PreloadResources() override {
std::cout << "Preloading menu textures and fonts...\n";
}
void LoadMainAssets() override {
std::cout << "Loading menu background and buttons...\n";
}
void InitializeSceneObjects() override {
std::cout << "Initializing menu UI elements...\n";
}
};
// 战斗场景加载
class BattleSceneLoader : public SceneLoader {
protected:
void PreloadResources() override {
std::cout << "Preloading battle assets (models, sounds)...\n";
}
void LoadMainAssets() override {
std::cout << "Loading map, characters and enemies...\n";
}
void InitializeSceneObjects() override {
std::cout << "Placing characters, setting up AI...\n";
}
void PostInitialization() override {
std::cout << "Starting background music for battle...\n";
}
bool EnableDebug() const override {
return true; // 战斗场景启用调试
}
void AttachDebugTools() override {
std::cout << "Attaching battle debug tools (HP bars, collision visualizer)...\n";
}
};
int main() {
std::unique_ptr<SceneLoader> menuLoader = std::make_unique<MainMenuLoader>();
menuLoader->LoadScene();
std::cout << "\n";
std::unique_ptr<SceneLoader> battleLoader = std::make_unique<BattleSceneLoader>();
battleLoader->LoadScene();
return 0;
}
3. 网络通信协议处理框架
#include <iostream>
#include <vector>
// 协议处理器基类
class ProtocolHandler {
public:
// 模板方法
void ProcessData(const std::vector<uint8_t>& rawData) {
if (!ValidateData(rawData)) {
HandleInvalidData();
return;
}
auto parsed = ParseData(rawData);
ProcessParsedData(parsed);
SendResponse();
if (NeedLogging()) {
LogTransaction(rawData);
}
}
virtual ~ProtocolHandler() = default;
protected:
virtual bool ValidateData(const std::vector<uint8_t>& data) = 0;
virtual std::string ParseData(const std::vector<uint8_t>& data) = 0;
virtual void ProcessParsedData(const std::string& parsedData) = 0;
virtual void SendResponse() {
std::cout << "Sending default response...\n";
}
virtual void HandleInvalidData() {
std::cerr << "Error: Invalid data received!\n";
}
// 钩子方法
virtual bool NeedLogging() const { return false; }
virtual void LogTransaction(const std::vector<uint8_t>& data) {
std::cout << "Logging transaction data...\n";
}
};
// HTTP协议处理器
class HttpProtocolHandler : public ProtocolHandler {
protected:
bool ValidateData(const std::vector<uint8_t>& data) override {
std::cout << "Validating HTTP headers...\n";
return data.size() > 10; // 简单示例验证
}
std::string ParseData(const std::vector<uint8_t>& data) override {
std::cout << "Parsing HTTP request...\n";
return "Parsed HTTP request";
}
void ProcessParsedData(const std::string& parsedData) override {
std::cout << "Processing HTTP request: " << parsedData << "\n";
}
void SendResponse() override {
std::cout << "Sending HTTP 200 OK response\n";
}
bool NeedLogging() const override { return true; }
};
// FTP协议处理器
class FtpProtocolHandler : public ProtocolHandler {
protected:
bool ValidateData(const std::vector<uint8_t>& data) override {
std::cout << "Validating FTP command...\n";
return !data.empty();
}
std::string ParseData(const std::vector<uint8_t>& data) override {
std::cout << "Parsing FTP command...\n";
return "Parsed FTP command";
}
void ProcessParsedData(const std::string& parsedData) override {
std::cout << "Executing FTP command: " << parsedData << "\n";
}
};
int main() {
std::vector<uint8_t> httpData = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
std::vector<uint8_t> ftpData = {0x41, 0x42, 0x43};
ProtocolHandler* httpHandler = new HttpProtocolHandler();
httpHandler->ProcessData(httpData);
delete httpHandler;
std::cout << "\n";
ProtocolHandler* ftpHandler = new FtpProtocolHandler();
ftpHandler->ProcessData(ftpData);
delete ftpHandler;
return 0;
}
开发中的关键点
-
识别固定流程:找出项目中那些有固定步骤但某些步骤需要变化的部分
-
设计钩子方法:
-
抽象方法:必须由子类实现
-
空方法:子类可选择实现
-
带默认实现的方法:子类可重写
-
-
控制反转:让父类控制流程,子类只负责具体实现
-
应用场景:
-
框架设计(如游戏引擎、网络库)
-
标准化流程(如文档导出、数据导入)
-
算法骨架固定但部分步骤可变的情况
-
这些示例展示了模板方法模式如何在实际项目中提供清晰的框架,同时保持足够的灵活性来处理各种具体实现。
模板方法模式是一种非常实用的设计模式,特别适用于框架设计,它定义了框架的流程,而将具体的实现细节留给子类去完成。