在软件开发中,我们经常会遇到需要根据不同情况选择不同算法或策略的场景。例如,电商网站的促销活动,针对不同会员等级或不同商品类型,可能采用不同的折扣策略。如果将这些策略直接硬编码到业务逻辑中,会导致代码冗余、难以维护和扩展。设计模式中的策略模式就是为了解决这类问题而生的。它允许我们定义一系列算法,并将每个算法封装到独立的策略类中,使得它们可以互相替换,而客户端代码无需关心具体策略的实现细节。
问题场景:电商平台的促销策略
假设我们正在开发一个电商平台的订单处理模块,需要根据用户等级和商品类型应用不同的促销策略。最初,我们可能会写出类似下面的代码:
#include <iostream>
#include <string>
class Order {
public:
std::string userId; // 用户ID
std::string productId; // 商品ID
double amount; // 订单金额
Order(std::string userId, std::string productId, double amount) : userId(userId), productId(productId), amount(amount) {}
double calculateDiscount() {
// 针对不同用户等级和商品类型应用不同的折扣
if (userId.substr(0, 1) == "V" && productId.substr(0, 1) == "P") { // VIP用户购买Premium商品
return amount * 0.2; // 20% 折扣
} else if (userId.substr(0, 1) == "V") { // VIP用户
return amount * 0.1; // 10% 折扣
} else if (productId.substr(0, 1) == "P") { // Premium商品
return amount * 0.05; // 5% 折扣
} else {
return 0.0; // 无折扣
}
}
};
int main() {
Order order1("V123", "P456", 100.0); // VIP用户购买Premium商品
Order order2("U789", "G012", 50.0); // 普通用户购买普通商品
std::cout << "Order 1 Discount: " << order1.calculateDiscount() << std::endl; // 输出: 20
std::cout << "Order 2 Discount: " << order2.calculateDiscount() << std::endl; // 输出: 0
return 0;
}
这段代码存在明显的问题:
- 紧耦合:
Order类与具体的折扣策略紧密耦合,一旦需要添加新的促销策略,就需要修改calculateDiscount方法,违反了开闭原则。 - 可读性差:
calculateDiscount方法包含大量的if-else语句,代码逻辑复杂,可读性差。 - 可维护性差:随着促销策略的增加,
calculateDiscount方法会越来越庞大,难以维护和扩展。
策略模式的解决方案
为了解决上述问题,我们可以使用策略模式。策略模式的核心思想是将不同的算法封装到独立的策略类中,并使用一个上下文类来选择和执行具体的策略。下面是使用策略模式重构后的代码:
#include <iostream>
#include <string>
// 抽象策略类
class DiscountStrategy {
public:
virtual double calculateDiscount(double amount) = 0;
virtual ~DiscountStrategy() = default;
};
// 具体策略类:VIP用户购买Premium商品策略
class VipPremiumDiscount : public DiscountStrategy {
public:
double calculateDiscount(double amount) override {
return amount * 0.2; // 20% 折扣
}
};
// 具体策略类:VIP用户策略
class VipDiscount : public DiscountStrategy {
public:
double calculateDiscount(double amount) override {
return amount * 0.1; // 10% 折扣
}
};
// 具体策略类:Premium商品策略
class PremiumDiscount : public DiscountStrategy {
public:
double calculateDiscount(double amount) override {
return amount * 0.05; // 5% 折扣
}
};
// 上下文类
class Order {
private:
DiscountStrategy* discountStrategy; // 指向策略类的指针
public:
std::string userId;
std::string productId;
double amount;
Order(std::string userId, std::string productId, double amount, DiscountStrategy* strategy) : userId(userId), productId(productId), amount(amount), discountStrategy(strategy) {}
void setDiscountStrategy(DiscountStrategy* strategy) {
discountStrategy = strategy;
}
double calculateDiscount() {
return discountStrategy->calculateDiscount(amount);
}
~Order() {
delete discountStrategy; // 释放策略类的内存
}
};
int main() {
// 使用不同的策略
Order order1("V123", "P456", 100.0, new VipPremiumDiscount());
Order order2("U789", "G012", 50.0, new PremiumDiscount());
Order order3("V789", "G012", 50.0, new VipDiscount());
Order order4("U789", "P456", 50.0, new PremiumDiscount());
Order order5("U789", "G012", 50.0, new DiscountStrategy()); // 这是一种错误用法, 因为DiscountStrategy是抽象类
std::cout << "Order 1 Discount: " << order1.calculateDiscount() << std::endl; // 输出: 20
std::cout << "Order 2 Discount: " << order2.calculateDiscount() << std::endl; // 输出: 2.5
std::cout << "Order 3 Discount: " << order3.calculateDiscount() << std::endl; // 输出: 5
std::cout << "Order 4 Discount: " << order4.calculateDiscount() << std::endl; // 输出: 2.5
return 0;
}
在这个重构后的代码中,我们定义了一个抽象策略类 DiscountStrategy,以及三个具体的策略类:VipPremiumDiscount、VipDiscount 和 PremiumDiscount。Order 类作为上下文类,持有一个指向 DiscountStrategy 的指针,并在 calculateDiscount 方法中调用具体策略的 calculateDiscount 方法。这样,我们可以根据不同的情况选择不同的策略,而无需修改 Order 类的代码。
策略模式的底层原理
策略模式的底层原理其实很简单,就是利用了面向对象编程中的多态性。通过定义一个抽象策略类,并让具体的策略类继承它,我们可以使用指向抽象策略类的指针来引用不同的具体策略类的对象。当调用 calculateDiscount 方法时,会根据实际对象的类型来执行相应的代码,这就是多态性的体现。
此外,策略模式还利用了组合关系。上下文类 Order 组合了策略类,这意味着 Order 类的行为可以动态地改变,而无需继承或修改其自身的代码。这种组合关系增强了代码的灵活性和可扩展性。
实战避坑经验总结
- 策略类的生命周期管理:在使用策略模式时,需要注意策略类的生命周期管理。在上面的代码中,我们在
Order类的析构函数中释放了策略类的内存。但是,如果策略类是由外部传入的,那么Order类就不应该负责释放它的内存。这时,可以考虑使用智能指针来管理策略类的生命周期,或者由客户端代码负责释放策略类的内存。 - 策略的选择:策略模式将策略的选择权交给了客户端代码,这意味着客户端代码需要知道有哪些策略可用,以及如何选择合适的策略。为了简化客户端代码,可以考虑使用工厂模式来创建策略对象,或者提供一个配置接口,让客户端代码可以通过配置来选择策略。
- 策略的共享:如果多个上下文类需要使用同一个策略对象,那么可以考虑使用单例模式来共享策略对象。这样可以减少内存占用,并提高性能。
- 避免过度设计:不要为了使用策略模式而使用它。如果业务逻辑本身并不复杂,硬编码可能更简单直接。策略模式的价值在于解决算法多变和复用的问题,如果只有少量固定策略,或者策略变化频率极低,过度使用策略模式反而会增加代码的复杂度。
与Nginx的联动思考
虽然本文讨论的是C++的策略模式,但其思想也适用于其他场景。例如,在 Nginx 的配置中,我们可以通过不同的 location 指令,匹配不同的 URL 规则,并配置不同的反向代理、负载均衡策略(例如轮询、IP Hash、加权轮询等)。每个 location 块可以看作一种策略,Nginx 根据请求的 URL 选择不同的 location 块执行,从而实现不同的处理逻辑。如果使用宝塔面板管理 Nginx,也能很方便的配置和切换不同的 Nginx 策略,应对高并发连接数等问题。
总结来说,策略模式是一种非常有用的设计模式,可以帮助我们构建灵活、可维护和可扩展的系统。通过将不同的算法封装到独立的策略类中,我们可以轻松地应对复杂多变的业务需求。
冠军资讯
代码一只喵