策略模式和模板方法模式的区别【面试题】
摘要:
策略模式和模板方法模式均属于行为设计模式,但核心差异显著。策略模式通过组合实现,支持运行时动态切换完整算法(如支付方式切换),变化维度大;模板方法模式通过继承实现,固定算法骨架但允许子类定制特定步骤(如文档生成流程不变,数据源可变),变化维度小。前者由调用方主动选择策略(控制权在客户端),后者遵循“好莱坞原则”由父类控制流程调用子类方法(控制权在父类)。选择依据:需灵活替换整套逻辑用策略模式;需复用流程但微调步骤用模板方法模式。
简单记忆:
第一,机制不同。模板方法靠继承,策略靠组合。这意味着模板方法在编译时就确定了行为扩展(通过子类化)相对变化小,而策略可以在运行时切换,变化的大。
第二,变化维度不同。模板方法变化的是算法中的某些步骤,整体流程不变;策略变化的是整个算法实现。就像做菜,模板方法是固定了“洗菜-切菜-烹饪”流程但允许换刀工,策略则是直接换菜系。
第三,控制方向相反。模板方法由父类控制流程调用子类方法(好莱坞原则),策略是调用方主动选择策略对象。
策略模式(Strategy Pattern)和模板方法模式(Template Method Pattern)都是行为设计模式,都用于封装变化的部分,但它们解决问题的角度、实现机制和适用场景有本质区别。以下是两者核心区别的清晰对比:
1)相同点
策略模式(Strategy Pattern)和模板方法模式(Template Method Pattern)都是行为设计模式,都用于封装变化的部分,但它们解决问题的角度、实现机制和适用场景有本质区别。以下是两者核心区别的清晰对比:
2)不同点
2.1)核心目标不同
- 策略模式:
替换整个算法。
目标是定义一系列可互换的算法,让客户端能根据需要动态切换不同的算法实现,且算法的变化不影响使用它的上下文。
👉 核心:算法的完整替换。 - 模板方法模式:
定义算法的骨架,允许子类重写特定步骤。
目标是固定一个操作的整体流程结构,仅允许子类修改流程中的某些步骤(变化点),而不改变算法的主干。
👉 核心:算法骨架不变,步骤细节可变。
2.2)实现机制不同
特性 | 策略模式 | 模板方法模式 |
---|---|---|
关系类型 | 组合(Has-a) | 继承(Is-a) |
关键参与者 | Context 持有 Strategy 接口的引用 | 抽象类定义 模板方法 + 抽象步骤方法 |
扩展方式 | 新增 Strategy 实现类 | 创建子类并实现抽象方法/覆盖钩子方法 |
运行时行为 | 可动态切换策略对象 | 子类行为在编译时确定(通过继承) |
控制流方向 | Context 委托给策略对象执行 | 父类调用子类方法(“好莱坞原则”) |
2.3)变化点不同
- 策略模式:
变化的是整个算法逻辑。
例如:支付时在支付宝
、信用卡
、PayPal
等完全不同的支付逻辑之间切换。 - 模板方法模式:
变化的是算法中的某些步骤,整体流程固定不变。
例如:文档生成流程打开模板->填充数据->保存文件
中,填充数据
的方式可变(填数据库数据/API数据),但步骤顺序不变。
2.4)代码结构对比
策略模式伪代码
// 策略接口
interface CompressionStrategy {void compress(File file);
}// 具体策略
class ZipCompression implements CompressionStrategy {void compress(File file) { /* ZIP压缩逻辑 */ }
}class RarCompression implements CompressionStrategy {void compress(File file) { /* RAR压缩逻辑 */ }
}// 上下文(组合策略)
class Compressor {private CompressionStrategy strategy;void setStrategy(CompressionStrategy s) { this.strategy = s; }void compressFile(File file) {strategy.compress(file); // 委托给当前策略}
}// 使用:动态切换策略
Compressor compressor = new Compressor();
compressor.setStrategy(new ZipCompression()); // 运行时切换为ZIP
compressor.compressFile(file);
模板方法模式伪代码
// 抽象类(定义模板骨架)
abstract class DataExporter {// 模板方法 (final 防止子类覆盖流程)public final void export() {openConnection();fetchData(); // 抽象方法 -> 子类实现transformData(); // 钩子方法 -> 子类可选覆盖save(); // 抽象方法 -> 子类实现closeConnection();}protected abstract void fetchData();protected abstract void save();protected void transformData() {} // 默认空实现(钩子方法)private void openConnection() { /* 通用逻辑 */ }private void closeConnection() { /* 通用逻辑 */ }
}// 具体子类
class CSVExporter extends DataExporter {protected void fetchData() { /* 从DB取数据 */ }protected void save() { /* 存为CSV */ }
}class JSONExporter extends DataExporter {protected void fetchData() { /* 从API取数据 */ }protected void save() { /* 存为JSON */ }protected void transformData() { /* 自定义数据转换 */ } // 覆盖钩子
}// 使用:子类继承固定流程
DataExporter exporter = new CSVExporter();
exporter.export(); // 执行固定流程+子类实现的步骤
2.5) 适用场景对比
场景 | 策略模式 | 模板方法模式 |
---|---|---|
算法需要动态切换 | ✅ 支付方式、排序算法、导航策略等 | ❌ 子类行为在编译时绑定 |
多个类有相同流程但不同步骤 | ❌ | ✅ 框架生命周期、文档生成、ETL流程 |
避免重复的流程代码 | ❌ | ✅ 将通用流程抽取到父类 |
隐藏算法实现细节 | ✅ 客户端只依赖策略接口 | ❌ 子类需知晓抽象步骤 |
扩展新算法/行为 | ✅ 新增策略类即可 | ✅ 新增子类实现抽象步骤 |
控制子类扩展点 | ❌ | ✅ 用 final 模板方法保护核心流程 |
关键总结:一句话区分
- 策略模式:“换整套方案”
→ 通过组合动态切换完整算法(如支付时换整套支付逻辑)。 - 模板方法模式:“固定流程,定制步骤”
→ 通过继承固定算法骨架,子类定制某些步骤(如报告生成流程固定,但数据来源和格式可定制)。
💡 简单记忆:
- 需要运行时灵活替换整个算法?→ 策略模式(组合)。
- 需要复用固定流程但允许步骤自定义?→ 模板方法模式(继承)。
喜欢我的文章记得点个在看,或者点赞,持续更新中ing…