解释
在面向对象系统的分析与设计过程中经常会遇到这样一种情况:对于某一个业务逻辑(算法实现)在不同的对象中有不同的细节实现,但是逻辑(算法)的框架(或通用的应用算法)是相同的。Template 提供了这种情况的一个实现框架Template 模式是采用继承的方式实现这一点:将逻辑(算法)框架放在抽象基类中,并定义好细节的接口,子类中实现细节。
一、应用场景示例:跨平台文件解析
假设需要开发支持Windows/Linux/MacOS的文件解析器,三类系统都需要以下步骤:
- 加载二进制文件
- 解析文件头
- 校验数据有效性
- 释放文件句柄
使用模板方法模式:
class FileParser {
public:
void parse() final { // 固化流程
loadBinary();
parseHeader();
validate();
releaseHandle();
}
protected:
virtual void loadBinary() = 0;
virtual void parseHeader() = 0;
virtual void validate() { /* 默认校验逻辑 */ }
void releaseHandle() { /* 通用释放逻辑 */ }
};
class WindowsParser : public FileParser {
protected:
void loadBinary() override {
/* Windows特有二进制加载 */
}
void parseHeader() override {
/* PE文件头解析 */
}
// 继承默认validate()
};
class LinuxParser : public FileParser {
protected:
void loadBinary() override {
/* ELF格式加载 */
}
void parseHeader() override {
/* ELF头解析 */
}
void validate() override {
/* 扩展校验逻辑 */
}
};
不使用模板方法模式的问题代码:
// 各平台独立实现流程控制(违反DRY原则)
class WindowsParser {
public:
void parse() {
// 必须重复实现完整流程
loadBinary(); // Windows实现
parseHeader(); // Windows实现
validate(); // 重复默认实现
releaseHandle(); // 重复通用实现
}
// 其他方法同上...
};
class LinuxParser {
public:
void parse() {
// 重复流程控制代码
loadBinary(); // Linux实现
parseHeader(); // Linux实现
validate(); // 定制实现
releaseHandle(); // 重复通用实现
}
// 其他方法同上...
};
二、不使用模板模式的缺点分析
- 代码冗余:每个子类都需要重复流程控制代码(如releaseHandle())
- 维护风险:修改流程时需要修改所有子类(例如新增日志步骤)
- 默认行为缺失:无法在基类中提供validate()的默认实现
- 接口不一致:容易遗漏步骤(如忘记调用releaseHandle())
三、关键场景对比
场景 | 使用模板模式 | 不用模板模式 |
---|---|---|
新增处理步骤 | 仅修改基类模板方法 | 需要修改所有实现类 |
流程异常处理 | 基类统一添加try-catch块 | 每个实现类单独处理 |
性能监控 | 在模板方法中添加计时逻辑 | 需在所有parse()方法中插入代码 |
默认行为覆盖 | 通过虚函数选择性重写 | 需要复制粘贴默认代码或继承普通类 |
四、特定缺陷示例:资源泄漏
// 错误示例:未强制释放资源
class MacParser {
public:
void parse() {
loadBinary();
parseHeader(); // 如果此处抛出异常
validate(); // 后续代码不会执行
// 忘记调用releaseHandle()
}
};
模板方法模式通过固定流程保证:
void FileParser::parse() final {
try {
loadBinary();
parseHeader();
validate();
} catch(...) {
// 统一异常处理
}
releaseHandle(); // 确保始终执行
}
结论:当存在多个相似流程的实现时,模板方法模式通过固化算法框架,从根本上避免了流程控制代码的重复和潜在风险。