策略模式:模式动机与定义

你有没有遇到过这种情况?

一个类里塞满了 if-else,每个分支都是一种算法。今天加一种,明天改一种。改着改着,代码就成了一团乱麻。我早年接手过一个支付模块,里面光支付方式就有七八种:微信、支付宝、银联、PayPal……每种支付的计算逻辑都不一样。那个类,啧啧,两千多行,改一次提心吊胆。

为什么会这样?

因为算法和客户端耦合得太紧了。你想想看,如果能把每种支付方式独立出来,各自封装成一个类,客户端只需要“选一个”来用,是不是清爽多了?

这就是策略模式要干的事。

策略模式的定义:定义一系列算法,把每一个算法封装起来,并且使它们可以互相替换。策略模式让算法独立于使用它的客户端而变化。

说白了,就是把“做什么”和“怎么做”分开。客户端只关心“我要做这件事”,至于具体怎么做,交给不同的策略对象去搞定。

角色与结构

策略模式的结构其实很简单,就三个角色。我画了一张图,你看一眼就明白了。

Context(上下文) Strategy(抽象策略) ConcreteStrategyA ConcreteStrategyB ConcreteStrategyC 持有策略引用 实现 实现 实现

三个角色各司其职:

  • Context(上下文):持有一个策略对象的引用。它不关心策略的具体实现,只负责调用策略的接口。我习惯把 Context 叫做“调度器”。
  • Strategy(抽象策略):定义所有策略必须实现的接口。通常是一个纯虚类或者接口类。
  • ConcreteStrategy(具体策略):实现抽象策略接口,封装具体的算法或行为。

嗯,这里要注意:Context 和 Strategy 之间是组合关系,不是继承。组合优于继承,这是设计模式的老生常谈了,但确实管用。

C++代码实现

咱们直接上代码。我以支付场景为例,写一个完整的策略模式实现。

// 抽象策略:支付方式
class PaymentStrategy {
public:
    virtual ~PaymentStrategy() = default;
    virtual void pay(double amount) = 0;
};

// 具体策略A:微信支付
class WeChatPay : public PaymentStrategy {
public:
    void pay(double amount) override {
        // 微信支付的具体逻辑
        std::cout << "使用微信支付:" << amount << "元" << std::endl;
        // 实际项目中这里会调用微信SDK
    }
};

// 具体策略B:支付宝支付
class AliPay : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "使用支付宝支付:" << amount << "元" << std::endl;
    }
};

// 具体策略C:银联支付
class UnionPay : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "使用银联支付:" << amount << "元" << std::endl;
    }
};

// Context:支付上下文
class PaymentContext {
private:
    std::unique_ptr<PaymentStrategy> strategy_;
public:
    explicit PaymentContext(std::unique_ptr<PaymentStrategy> strategy)
        : strategy_(std::move(strategy)) {}

    void setStrategy(std::unique_ptr<PaymentStrategy> strategy) {
        strategy_ = std::move(strategy);
    }

    void executePayment(double amount) {
        if (!strategy_) {
            std::cerr << "错误:未设置支付策略" << std::endl;
            return;
        }
        strategy_->pay(amount);
    }
};

// 客户端使用
int main() {
    PaymentContext context(std::make_unique<WeChatPay>());
    context.executePayment(99.9);

    // 运行时切换策略
    context.setStrategy(std::make_unique<AliPay>());
    context.executePayment(150.0);

    return 0;
}

个人经验:我在项目中遇到过一种情况——策略对象需要携带一些配置参数。比如微信支付需要 appId,支付宝需要商户号。我的做法是:不在策略接口里暴露这些细节,而是在具体策略的构造函数里传入。Context 只负责传递,不负责解析。这样保持了接口的干净。

算法族封装

策略模式最厉害的地方,就是能把一整个“算法族”给封装起来。什么叫算法族?就是一群功能相似、但实现方式不同的算法。

举个例子:

  • 排序算法族:冒泡排序、快速排序、归并排序……
  • 压缩算法族:ZIP、GZIP、BZIP2……
  • 加密算法族:AES、RSA、SM4……

你想想看,如果没有策略模式,你要怎么切换排序算法?大概率是写一个巨大的函数,里面塞满 if-else。每次新增一种排序,都要改这个函数,还要担心改坏了原有的逻辑。

用策略模式,每种排序算法独立成一个类。新增算法?写一个新类,实现排序接口,完事。客户端想用哪种,就传哪种。这就是“开闭原则”——对扩展开放,对修改关闭。

核心思想:把变化的部分封装起来,让不变的部分稳定下来。策略模式封装的是“行为”的变化。

我曾经在一个图像处理项目里用过这个思路。图像滤镜有十几种:模糊、锐化、浮雕、边缘检测……每种滤镜的算法完全不同。我把每个滤镜封装成一个策略,客户端可以动态组合滤镜链。后来产品经理说要加一个新滤镜,我只花了一个小时就搞定了——写一个新策略类,注册进去,完事。没有改任何现有代码。

避坑指南:策略模式不是万能的。如果你的算法只有两三种,而且几乎不变,硬上策略模式反而增加了代码复杂度。我曾经见过一个项目,里面只有两种日志输出方式(控制台和文件),也用了策略模式,搞了四个文件。这就过度设计了。记住:模式是用来解决问题的,不是用来炫技的。

另外,策略模式的一个小缺点是客户端必须知道所有策略的差异。如果策略太多,客户端的选择成本会变高。这时候可以结合工厂模式,让工厂帮你选策略。嗯,那是另一个话题了。

最后总结一下策略模式的适用场景:

场景 说明
多个类只有行为不同 可以用策略模式避免大量子类
需要动态选择算法 运行时切换策略,灵活性高
算法分支很多且经常变化 策略模式让新增算法变得安全
想避免条件判断的硬编码 把 if-else 拆成多个策略类

好了,策略模式就聊到这里。记住它的核心:封装变化,组合优先。下次你写代码时,看到一堆 if-else 在选算法,不妨停下来想想——是不是该用策略模式了?

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