核心区别一句话概括:
- 策略模式: 解决的是 “做什么” 的问题,它允许你动态替换整个算法或策略。
- 模板模式: 解决的是 “怎么做” 的问题,它定义了算法的骨架,但允许子类在不改变结构的情况下重写特定步骤。
下面我们通过一个项目的、非常贴切的例子来深入理解。
一、核心思想对比
| 特性 | 策略模式 (Strategy Pattern) | 模板模式 (Template Pattern) |
|---|---|---|
| 意图 | 定义一系列算法,封装它们,并且使它们可以相互替换。 | 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 |
| 核心原则 | 组合优于继承。通过持有策略接口的引用来使用算法。 | 继承。通过子类来扩展或重新定义算法的特定步骤。 |
| 关注点 | 替换整个算法。客户端决定使用哪种策略。 | 复用和扩展算法结构。父类控制流程,子类实现细节。 |
| 灵活性 | 运行时灵活。可以在运行时动态切换不同的策略。 | 编译时确定。通过创建不同的子类来改变行为,通常在编译时就已确定。 |
| 代码复用 | 策略之间一般不共享代码。每个策略都是独立的。 | 在父类中高度复用模板方法中的流程代码。 |
二、具体项目分析
假设在金融领域中,有一个计算“投资组合风险评级”的功能。
场景:使用【策略模式】
-
问题:不同的金融机构(或不同产品线)可能使用完全不同的算法来计算风险评级。比如:
- 算法A:基于波动率和夏普比率。
- 算法B:基于最大回撤和VaR(风险价值)。
- 算法C:一套自定义的复杂规则。
你需要能够灵活地切换这些算法,甚至未来新增算法D。
-
实现:
1.定义一个策略接口 RiskCalculationStrategy。
2.为每种算法实现一个具体的策略类:VolatilitySharpeStrategy, DrawdownVarStrategy, CustomRuleStrategy。
3.在风险计算服务中持有策略接口的引用,并由客户端(或配置)决定注入哪种策略。
// 1. 策略接口
public interface RiskCalculationStrategy {
String calculateRisk(Portfolio portfolio);
}
// 2. 具体策略实现
@Component
public class VolatilitySharpeStrategy implements RiskCalculationStrategy {
@Override
public String calculateRisk(Portfolio portfolio) {
// 完全基于波动率和夏普比率的算法实现
double volatility = calculateVolatility(portfolio);
double sharpe = calculateSharpeRatio(portfolio);
// ... 计算逻辑
return "A"; // 返回风险等级
}
}
@Component
public class DrawdownVarStrategy implements RiskCalculationStrategy {
@Override
public String calculateRisk(Portfolio portfolio) {
// 完全不同的另一套算法实现:基于回撤和VaR
double maxDrawdown = calculateMaxDrawdown(portfolio);
double var = calculateVar(portfolio);
// ... 计算逻辑
return "B";
}
}
// 3. 环境类 (Context) - 使用策略
@Service
public class RiskAssessmentService {
private RiskCalculationStrategy strategy;
//通过配置文件的形式,读取配置文件 ${app.risk.strategy} 获取想要实现的策略
public RiskAssessmentService(Map<String, RiskCalculationStrategy> strategyMap,
@Value("${app.risk.strategy}") String strategyBeanName) {
this.strategy = strategyMap.get(strategyBeanName);
if (this.strategy == null) {
throw new IllegalStateException("Unknown strategy bean: " + strategyBeanName);
}
}
public String assessRisk(Portfolio portfolio) {
// 委托给策略对象执行计算
return strategy.calculateRisk(portfolio);
}
}
场景:使用【模板模式】
- 问题:公司规定,所有风险评级报告必须遵循一个严格的生成流程:
步骤一:获取原始数据。
步骤二:验证数据有效性。
步骤三:计算风险指标。 <- 只有这个核心计算步骤的方法不同
步骤四:格式化报告。
步骤五:发送通知。
这个流程是固定的,但步骤三的计算方法有多种(如上文的A、B、C算法)。 - 实现:
- 定义一个抽象类 AbstractRiskReportGenerator,它包含一个 final 的模板方法 generateReport(),该方法按顺序调用各个步骤。
- 将公共步骤(1,2,4,5)在抽象类中实现。
- 将变化的步骤(3)声明为抽象方法 calculateRisk()。
- 创建不同的子类来继承这个抽象类,并只实现 calculateRisk() 方法。
// 1. 抽象模板类
public abstract class AbstractRiskReportGenerator {
// 【核心】final的模板方法,定义了不可更改的算法骨架
public final Report generateReport(Portfolio portfolio) {
// 步骤1: 获取数据 (公共实现)
Data data = fetchData(portfolio);
// 步骤2: 验证数据 (公共实现)
validateData(data);
// 步骤3: 计算风险 (抽象,留给子类实现) <- 这是可变的步骤
String riskRating = calculateRisk(data);
// 步骤4: 格式化报告 (公共实现)
Report report = formatReport(riskRating);
// 步骤5: 发送通知 (公共实现)
sendNotification(report);
return report;
}
// 公共步骤的实现
private Data fetchData(Portfolio p) { ... }
private void validateData(Data d) { ... }
private Report formatReport(String r) { ... }
private void sendNotification(Report r) { ... }
// 抽象方法,声明为protected,要求子类必须实现
protected abstract String calculateRisk(Data data);
}
// 2. 具体子类实现
@Service
public class VolatilityRiskReportGenerator extends AbstractRiskReportGenerator {
@Override
protected String calculateRisk(Data data) {
// 子类只需关注如何计算风险,而不关心整个报告流程
double volatility = calculateVolatility(data);
double sharpe = calculateSharpeRatio(data);
// ... 计算逻辑
return "A";
}
}
@Service
public class DrawdownRiskReportGenerator extends AbstractRiskReportGenerator {
@Override
protected String calculateRisk(Data data) {
// 另一个子类用另一种方式实现计算步骤
double maxDrawdown = calculateMaxDrawdown(data);
double var = calculateVar(data);
// ... 计算逻辑
return "B";
}
}
要点:子类无法改变生成报告的流程(因为模板方法是 final 的),它只能去定制流程中的某一个特定步骤(calculateRisk)。这是“怎么做”的定制。
三、总结与如何选择
| 模式 | 选择时机 | 你的项目中的可能应用场景 |
|---|---|---|
| 策略模式 | 当你有多个完全不同的算法需要在运行时动态选择时。 | 收费中心:叠佣模式、提佣模式、后端分成模式等不同的计算策略,可以动态切换。 |
| 模板模式 当你有一个固定的流程,但流程中的某一个或几个步骤有多种实现时。 | 批量计算任务:定义一个AbstractCalculationTask模板,其中fetchData(), calculate(), saveResult()是步骤,子类实现具体的计算逻辑。 |
关键区别再现:
想象一下做菜:
-
策略模式:就像选择完全不同菜系的厨师(川菜厨师、粤菜厨师、意大利厨师)。你选择哪个厨师,就决定了整道菜完全不同的做法和风味。
-
模板模式:就像一份标准化的菜谱,规定了“焯水 -> 翻炒 -> 勾芡 -> 装盘”的固定流程。不同的厨师(子类)遵循同一个流程,但在“翻炒”这个步骤上,放入的调料和火候掌握(实现)不同,从而做出略有差异的菜。

被折叠的 条评论
为什么被折叠?



