40. 设计模式面试题5:实现一个策略模式,支持运行时切换算法
策略模式,说白了就是「把算法封装起来,让它们可以互相替换」。我面试的时候特别喜欢问这个模式,因为它能很直观地考察一个人对「开闭原则」的理解深度。
你想想看,如果代码里到处都是 if-else 或者 switch-case,每次加一个新算法就要改核心逻辑,那维护起来得多痛苦?策略模式就是来解决这个问题的。
什么是策略模式?
策略模式定义了一系列算法,把它们一个个封装起来,并且使它们可以相互替换。核心思想是:将算法的定义和使用分离。
我记得有一次接手一个老项目,里面有个计算折扣的模块,写了十几个 if-else 分支。每次促销活动都要改这个函数,改一次线上出一次 bug。后来我重构成了策略模式,新加折扣只需要新增一个策略类,完全不用动原有代码。
核心三要素:
- 策略接口(Strategy):定义所有算法的公共接口
- 具体策略(ConcreteStrategy):实现具体的算法逻辑
- 上下文(Context):持有策略对象的引用,负责调用策略
运行时切换的关键
为什么能运行时切换?因为上下文里存的是策略接口的指针或引用。你只需要在运行时改变这个指针指向的具体策略对象,行为就立刻变了。
这里有个坑——我曾经见过有人把策略对象直接嵌在上下文里,用 union 或者 std::variant 来切换。嗯,这样做虽然也能工作,但每加一个新策略就要改上下文类的定义,违背了开闭原则。我个人习惯还是用多态,虽然有一点点运行时开销,但扩展性好了不止一个量级。
代码示例:一个支付系统
假设我们要实现一个支付系统,支持支付宝、微信支付和银行卡支付,并且允许用户在运行时切换支付方式。
// 策略接口
class PaymentStrategy {
public:
virtual ~PaymentStrategy() = default;
virtual void pay(double amount) = 0;
virtual std::string getMethodName() const = 0;
};
// 具体策略:支付宝
class AlipayStrategy : public PaymentStrategy {
public:
void pay(double amount) override {
std::cout << "使用支付宝支付: " << amount << " 元" << std::endl;
// 实际的支付宝支付逻辑...
}
std::string getMethodName() const override {
return "支付宝";
}
};
// 具体策略:微信支付
class WechatPayStrategy : public PaymentStrategy {
public:
void pay(double amount) override {
std::cout << "使用微信支付: " << amount << " 元" << std::endl;
// 实际的微信支付逻辑...
}
std::string getMethodName() const override {
return "微信支付";
}
};
// 具体策略:银行卡
class CardPayStrategy : public PaymentStrategy {
public:
void pay(double amount) override {
std::cout << "使用银行卡支付: " << amount << " 元" << std::endl;
// 实际的银行卡支付逻辑...
}
std::string getMethodName() const override {
return "银行卡";
}
};
// 上下文:支付上下文
class PaymentContext {
private:
std::unique_ptr<PaymentStrategy> strategy_;
public:
// 运行时切换策略
void setStrategy(std::unique_ptr<PaymentStrategy> strategy) {
strategy_ = std::move(strategy);
std::cout << "已切换至: " << strategy_->getMethodName() << std::endl;
}
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);
// 用户又切换为银行卡
context.setStrategy(std::make_unique<CardPayStrategy>());
context.executePayment(299.9);
return 0;
}
策略模式的结构图
策略模式的优缺点
| 优点 | 缺点 |
|---|---|
|
|
小提示:如果策略类数量不多且相对稳定,可以考虑用函数指针或 std::function 替代多态,减少类数量。但一旦策略数量可能增长,还是老老实实用多态吧。
避坑指南:我曾经见过有人把策略模式用成了「策略反模式」——上下文里存了策略指针,但又在上下文里写 if-else 来判断该调用哪个策略。那还不如直接用 if-else 呢!策略模式的核心是「把判断逻辑交给客户端」,上下文只负责调用,不负责选择。
什么时候用策略模式?
我个人总结了几条经验:
- 一个系统需要在多种算法中选择一种,且这些算法可以互相替换
- 算法逻辑复杂,不适合放在一个函数里用 if-else 堆砌
- 算法可能会扩展,你希望未来的改动不影响现有代码
- 需要运行时动态改变行为
说白了,如果你发现代码里有一长串 if-else 或者 switch-case,而且每个分支做的事情差异很大,那就是策略模式出场的时候了。
嗯,面试的时候我还会追问一个问题:「策略模式和状态模式有什么区别?」这个问题留给你自己思考。简单提示一下:策略模式是客户端主动选择算法,状态模式是对象内部状态自动驱动行为变化。想清楚这个区别,你对这两个模式的理解就更深一层了。