17、策略模式:模式动机与定义、角色与结构、C++代码实现、消除if-else
模式动机:为什么我们需要策略模式?
先问大家一个问题:你写过那种「一个函数里塞了七八个if-else」的代码吗?
我写过,而且不止一次。记得刚工作那会儿,我负责一个支付模块。用户可以选择支付宝、微信、银行卡、甚至积分抵扣。我的第一版代码长什么样?一个巨大的switch-case,每个case里调不同的API。看起来挺直白的,对吧?
但问题很快就来了。产品经理说:「加一个云闪付吧。」我打开那个几百行的函数,小心翼翼地找到合适的位置,插入新的case。测试通过了,上线了。两周后,产品经理又说:「把积分抵扣的逻辑改一下。」我又打开那个函数,开始找积分相关的代码……你想想看,这种代码维护起来有多痛苦?
说白了,策略模式就是为了解决「算法族」的封装和互换问题。当你的代码里出现大量条件分支,而且每个分支做的事情本质上是一类操作的不同实现时,就该考虑策略模式了。
模式定义:一句话说清楚
策略模式定义了一族算法,分别封装起来,让它们之间可以互相替换。这个模式让算法的变化独立于使用算法的客户。
嗯,这里要注意:策略模式不是用来消灭if-else的,而是用来管理if-else的。如果只有两三个分支,写if-else完全没问题。但当分支数量膨胀到五六个、七八个,而且每个分支的逻辑还在不断变化时,策略模式就是你的救星。
角色与结构:三个核心角色
策略模式的结构其实很简单,就三个角色:
| 角色 | 名称 | 职责 |
|---|---|---|
| Context(上下文) | 环境类 | 维护一个对策略对象的引用,负责调用策略 |
| Strategy(策略) | 抽象策略类 | 定义所有支持的算法的公共接口 |
| ConcreteStrategy(具体策略) | 具体策略类 | 实现具体的算法逻辑 |
我个人的习惯是:先把抽象策略接口定义好,然后每个具体策略单独一个类。Context里只保留一个指向策略接口的指针或引用。这样,新增一种策略,我只需要加一个新类,完全不用改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,但一定能让你的代码更清晰、更易维护。