17、策略模式:模式动机与定义、角色与结构、C++代码实现、消除if-else

模式动机:为什么我们需要策略模式?

先问大家一个问题:你写过那种「一个函数里塞了七八个if-else」的代码吗?

我写过,而且不止一次。记得刚工作那会儿,我负责一个支付模块。用户可以选择支付宝、微信、银行卡、甚至积分抵扣。我的第一版代码长什么样?一个巨大的switch-case,每个case里调不同的API。看起来挺直白的,对吧?

但问题很快就来了。产品经理说:「加一个云闪付吧。」我打开那个几百行的函数,小心翼翼地找到合适的位置,插入新的case。测试通过了,上线了。两周后,产品经理又说:「把积分抵扣的逻辑改一下。」我又打开那个函数,开始找积分相关的代码……你想想看,这种代码维护起来有多痛苦?

说白了,策略模式就是为了解决「算法族」的封装和互换问题。当你的代码里出现大量条件分支,而且每个分支做的事情本质上是一类操作的不同实现时,就该考虑策略模式了。

核心动机:将算法(行为)从使用它的客户端中独立出来,让它们可以互相替换,且不影响客户端。

模式定义:一句话说清楚

策略模式定义了一族算法,分别封装起来,让它们之间可以互相替换。这个模式让算法的变化独立于使用算法的客户。

嗯,这里要注意:策略模式不是用来消灭if-else的,而是用来管理if-else的。如果只有两三个分支,写if-else完全没问题。但当分支数量膨胀到五六个、七八个,而且每个分支的逻辑还在不断变化时,策略模式就是你的救星。

角色与结构:三个核心角色

策略模式的结构其实很简单,就三个角色:

角色 名称 职责
Context(上下文) 环境类 维护一个对策略对象的引用,负责调用策略
Strategy(策略) 抽象策略类 定义所有支持的算法的公共接口
ConcreteStrategy(具体策略) 具体策略类 实现具体的算法逻辑

我个人的习惯是:先把抽象策略接口定义好,然后每个具体策略单独一个类。Context里只保留一个指向策略接口的指针或引用。这样,新增一种策略,我只需要加一个新类,完全不用改Context的代码。

Context - strategy: Strategy* + executeStrategy() «interface» Strategy + execute() ConcreteStrategyA + execute() ConcreteStrategyB + execute() ConcreteStrategyC + execute() Context 持有 Strategy 接口,运行时可以切换具体策略 新增策略只需添加新类,无需修改 Context

C++代码实现:从支付场景说起

回到我那个支付模块的例子。我们用策略模式来重构它。

首先,定义抽象策略接口:

// 支付策略接口
class PaymentStrategy {
public:
    virtual ~PaymentStrategy() = default;
    virtual void pay(double amount) = 0;
};

然后,实现几个具体策略:

// 支付宝支付
class AlipayStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "使用支付宝支付: " << amount << " 元" << std::endl;
        // 调用支付宝SDK...
    }
};

// 微信支付
class WechatPayStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "使用微信支付: " << amount << " 元" << std::endl;
        // 调用微信SDK...
    }
};

// 银行卡支付
class BankCardStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "使用银行卡支付: " << amount << " 元" << std::endl;
        // 调用银行接口...
    }
};

接下来是Context类:

class PaymentContext {
private:
    std::unique_ptr<PaymentStrategy> strategy_;

public:
    // 运行时设置策略
    void setStrategy(std::unique_ptr<PaymentStrategy> strategy) {
        strategy_ = std::move(strategy);
    }

    void executePayment(double amount) {
        if (!strategy_) {
            throw std::runtime_error("未设置支付策略");
        }
        strategy_->pay(amount);
    }
};

客户端代码怎么用?

int main() {
    PaymentContext context;

    // 用户选择了支付宝
    context.setStrategy(std::make_unique<AlipayStrategy>());
    context.executePayment(99.9);

    // 用户切换到了微信支付
    context.setStrategy(std::make_unique<WechatPayStrategy>());
    context.executePayment(199.9);

    return 0;
}
我的经验:实际项目中,策略对象通常不是这样手动创建的。我会用一个工厂方法,根据用户输入(比如支付方式字符串)来创建对应的策略。这样客户端代码就更干净了。

消除if-else:策略模式的核心价值

好了,现在我们来对比一下。没有策略模式之前,你的代码可能是这样的:

void processPayment(const std::string& method, double amount) {
    if (method == "alipay") {
        // 支付宝逻辑...
        std::cout << "支付宝支付 " << amount << std::endl;
    } else if (method == "wechat") {
        // 微信逻辑...
        std::cout << "微信支付 " << amount << std::endl;
    } else if (method == "bankcard") {
        // 银行卡逻辑...
        std::cout << "银行卡支付 " << amount << std::endl;
    } else {
        throw std::invalid_argument("不支持的支付方式");
    }
}

这段代码有什么问题?

  • 可扩展性差:每加一种支付方式,就要改这个函数
  • 可读性差:所有逻辑混在一起,函数越来越长
  • 违反开闭原则:对修改开放,对扩展不开放

用了策略模式之后,if-else被转移到了策略的创建环节。你可以在工厂里用if-else,也可以在配置文件中映射。但关键是:业务逻辑不再和条件分支耦合在一起

消除if-else的真正含义:不是代码里没有if-else了,而是把条件判断从「业务逻辑」中剥离出来,放到「策略选择」的环节。这样业务逻辑就变成了一个个独立的策略类,各自为政,互不干扰。

避坑指南:我曾经踩过的坑

我曾经在一个项目中过度使用策略模式。当时有个功能只有两种算法,我也给它们各自建了一个策略类。结果代码量翻了一倍,维护起来反而更麻烦。

这里给大家几个建议:

  • 不要为了模式而模式:如果只有两三个分支,而且逻辑稳定不变,用if-else完全没问题
  • 策略类不要太大:每个策略只做一件事。如果一个策略类里塞了太多逻辑,说明你的策略粒度不对
  • 注意策略的生命周期:如果策略对象是无状态的,可以考虑复用,避免频繁创建销毁
  • 结合工厂模式使用:策略的创建通常交给工厂,这样客户端只需要知道策略的「名字」
特别注意:策略模式会增加类的数量。如果你的项目里策略类超过10个,建议考虑用「策略注册表」来管理,或者用函数指针/lambda表达式替代类,避免类爆炸。

总结

策略模式是我个人非常喜欢的一个模式。它简单、实用,而且能很好地体现「面向接口编程」的思想。说白了,它就是教我们:把变化的部分封装起来,让它们可以独立演化

下次你看到代码里有一长串if-else,而且每个分支做的事情都类似,不妨想想策略模式。它不一定能消灭所有if-else,但一定能让你的代码更清晰、更易维护。

公众号:蓝海资料掘金营,微信deep3321