看了很多文章还分不清策略和模板模式?这篇保姆级图解带你彻底搞懂!

核心区别一句话概括:

  • 策略模式: 解决的是 “做什么” 的问题,它允许你动态替换整个算法或策略。
  • 模板模式: 解决的是 “怎么做” 的问题,它定义了算法的骨架,但允许子类在不改变结构的情况下重写特定步骤。

下面我们通过一个项目的、非常贴切的例子来深入理解。

一、核心思想对比

特性策略模式 (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()是步骤,子类实现具体的计算逻辑。

关键区别再现:
想象一下做菜:

  • 策略模式:就像选择完全不同菜系的厨师(川菜厨师、粤菜厨师、意大利厨师)。你选择哪个厨师,就决定了整道菜完全不同的做法和风味。

  • 模板模式:就像一份标准化的菜谱,规定了“焯水 -> 翻炒 -> 勾芡 -> 装盘”的固定流程。不同的厨师(子类)遵循同一个流程,但在“翻炒”这个步骤上,放入的调料和火候掌握(实现)不同,从而做出略有差异的菜。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值