在实际的项目开发中,我们经常会遇到需要根据不同条件执行不同算法的情况。最常见的做法就是使用大量的 if-else 或 switch 语句。但是,当算法种类繁多,或者算法逻辑复杂时,这种方式会导致代码臃肿、难以维护,并且扩展性差。本文将深入探讨策略模式,通过 C++ 示例代码,讲解如何使用策略模式优雅地解决这类问题。
问题场景重现:不同优惠策略计算订单价格
假设我们正在开发一个电商平台的订单系统,需要根据不同的用户类型(例如:普通用户、VIP 用户、SVIP 用户)应用不同的优惠策略来计算最终订单价格。
最直接的方式是这样:
#include <iostream>
class Order {
public:
double calculateTotalPrice(double originalPrice, std::string userType) {
if (userType == "normal") {
// 普通用户,不打折
return originalPrice;
} else if (userType == "vip") {
// VIP 用户,打 9 折
return originalPrice * 0.9;
} else if (userType == "svip") {
// SVIP 用户,打 8 折
return originalPrice * 0.8;
} else {
// 未知用户类型,不打折
return originalPrice;
}
}
};
int main() {
Order order;
std::cout << "Normal user price: " << order.calculateTotalPrice(100.0, "normal") << std::endl; // 输出 100
std::cout << "VIP user price: " << order.calculateTotalPrice(100.0, "vip") << std::endl; // 输出 90
std::cout << "SVIP user price: " << order.calculateTotalPrice(100.0, "svip") << std::endl; // 输出 80
return 0;
}
这段代码简单易懂,但存在明显的问题:
- 违反开闭原则: 如果要添加新的用户类型和优惠策略,就需要修改
Order类的calculateTotalPrice方法。 - 代码可读性差: 大量的
if-else语句使得代码逻辑混乱。 - 维护困难: 随着优惠策略的增加,
calculateTotalPrice方法会变得越来越庞大,难以维护。
底层原理深度剖析:策略模式的核心思想
策略模式 是一种行为型设计模式,它定义了一系列的算法,并将每一个算法封装到一个独立的类中,从而使得算法可以在不影响客户端的情况下相互替换。其核心思想是将算法的定义和使用分离,使得算法可以独立于客户端变化。
策略模式包含以下几个核心角色:
- Context(环境类): 持有一个 Strategy 接口的引用,负责选择并执行具体的策略。
- Strategy(抽象策略类): 定义所有支持的算法的公共接口。
- ConcreteStrategy(具体策略类): 实现 Strategy 接口,封装具体的算法。
具体代码解决方案:C++ 实现策略模式
下面我们使用策略模式来重构上面的代码:
#include <iostream>
#include <string>
// 1. 抽象策略类
class DiscountStrategy {
public:
virtual double applyDiscount(double price) = 0; // 纯虚函数,定义折扣策略接口
virtual ~DiscountStrategy() = default; // 虚析构函数,防止内存泄漏
};
// 2. 具体策略类 - Normal 策略
class NormalDiscount : public DiscountStrategy {
public:
double applyDiscount(double price) override {
return price; // 不打折
}
};
// 3. 具体策略类 - VIP 策略
class VipDiscount : public DiscountStrategy {
public:
double applyDiscount(double price) override {
return price * 0.9; // 打 9 折
}
};
// 4. 具体策略类 - SVIP 策略
class SvipDiscount : public DiscountStrategy {
public:
double applyDiscount(double price) override {
return price * 0.8; // 打 8 折
}
};
// 5. 环境类
class Order {
private:
DiscountStrategy* discountStrategy; // 持有策略类的指针
public:
Order(DiscountStrategy* strategy) : discountStrategy(strategy) {}
void setDiscountStrategy(DiscountStrategy* strategy) {
discountStrategy = strategy; // 动态切换策略
}
double calculateTotalPrice(double originalPrice) {
return discountStrategy->applyDiscount(originalPrice); // 调用策略类的 applyDiscount 方法
}
};
int main() {
NormalDiscount normalDiscount;
VipDiscount vipDiscount;
SvipDiscount svipDiscount;
Order order1(&normalDiscount);
std::cout << "Normal user price: " << order1.calculateTotalPrice(100.0) << std::endl; // 输出 100
Order order2(&vipDiscount);
std::cout << "VIP user price: " << order2.calculateTotalPrice(100.0) << std::endl; // 输出 90
Order order3(&svipDiscount);
std::cout << "SVIP user price: " << order3.calculateTotalPrice(100.0) << std::endl; // 输出 80
//动态切换策略
order3.setDiscountStrategy(&normalDiscount);
std::cout << "SVIP user switch to Normal user price: " << order3.calculateTotalPrice(100.0) << std::endl; // 输出 100
return 0;
}
与之前的代码相比,这段代码具有以下优点:
- 符合开闭原则: 如果要添加新的优惠策略,只需要添加一个新的
ConcreteStrategy类,而不需要修改Order类。 - 代码可读性好: 每个策略类都封装了一个具体的算法,代码逻辑清晰。
- 维护方便: 每个策略类都是独立的,可以单独进行维护。
此外,我们还可以结合工厂模式,根据用户类型动态地创建对应的 DiscountStrategy 对象,进一步提高代码的灵活性。
实战避坑经验总结
- 避免过度设计: 策略模式适用于算法需要频繁切换的场景。如果算法很少变化,或者只有少量算法,使用简单的
if-else语句可能更合适。 - 注意内存管理: 在 C++ 中,如果使用指针来持有
Strategy对象,需要注意内存管理,避免内存泄漏。可以使用智能指针(例如std::unique_ptr)来自动管理内存。 - 策略类的粒度: 策略类的粒度应该适中。如果策略类的粒度太小,会导致类的数量过多;如果策略类的粒度太大,会导致策略类的功能过于复杂。
- 结合其他设计模式: 策略模式可以与其他设计模式结合使用,例如工厂模式、模板方法模式等,以实现更复杂的功能。
在实际项目中,合理运用策略模式能够显著提高代码的可维护性和扩展性。例如,在 Nginx 配置中,可以通过不同的 upstream 配置实现负载均衡,选择不同的负载均衡算法 (例如:轮询、IP Hash、加权轮询等),也体现了策略模式的思想。后端架构师在设计高并发系统时,也需要考虑这些设计模式,优化系统性能,提升用户体验。可以使用宝塔面板等工具进行快速部署和管理,方便进行配置更改和测试。
冠军资讯
代码一只喵