概述
与前一篇文章中提到的观察者模式一样,策略模式也是一种行为设计模式。它允许我们定义一系列算法,并将每个算法封装起来,使它们可以互换使用。通过这种方式,策略模式使得算法的变化独立于使用这些算法的客户端,从而提高了系统的灵活性和可维护性。
商场的折扣计算是运用策略模式的一个典型例子:在促销活动中,顾客可以享受不同的折扣,包括满减优惠、会员专享折扣、节日特惠等;当顾客结账时,系统可以根据实际情况选择适用的折扣策略进行计算。这种设计使得折扣规则易于管理和扩展,同时也提高了系统的灵活性。
基本原理
用一句话来描述策略模式的话,实际上就是将一组相关的行为抽象出来,让它们可以被替换。这意味着,我们可以根据具体情况动态地选择最适合的行为或算法,而不必修改现有代码结构。这种设计不仅遵循了面向对象编程中的开闭原则(对扩展开放,对修改关闭),还大大简化了条件逻辑。策略模式包括如下三个核心组件。
1、策略接口。定义了一个公共接口,所有的具体策略都必须实现这个接口。这使得我们可以调用任何具体策略,而无需关心其内部实现细节。
2、具体策略类。实现了策略接口的具体类,每个具体策略类代表了一种不同的算法或行为逻辑,这些类包含了实际执行任务的代码。
3、上下文类。上下文类负责与具体的策略进行交互,它持有策略对象的引用,并提供一个接口供客户端调用,以执行所选策略的操作。
基于上面的核心组件,策略模式的实现主要有以下五个步骤。
1、定义策略接口。首先,我们需要定义一个抽象的策略接口或基类。这个接口声明了所有具体策略必须实现的方法,这些方法代表了不同的算法或行为逻辑。
2、实现具体策略。根据实际需求,为每种不同的算法或行为逻辑创建具体策略类。每个具体策略类都应该继承自上面定义的策略接口,并且提供具体的算法实现。
3、设计上下文类。上下文类负责与具体策略进行交互,它持有一个策略对象的引用,并通过该引用调用策略的具体实现。另外,上下文类还应该提供设置和获取当前策略的方法,以允许外部更改所使用的策略。
4、配置策略。在应用程序启动或运行过程中,根据具体情况选择适当的策略,并将其传递给上下文。
5、执行策略。当需要执行某个行为时,上下文会调用其持有的策略对象的方法,从而触发相应的行为或算法。
实战解析
在下面的实战代码中,我们使用策略模式实现了购物车的折扣计算。
首先,我们定义了一个抽象类CDiscountStrategy作为所有具体折扣策略的接口,它包含一个纯虚函数Calculate用于定义价格计算逻辑。
接着,我们实现了三个具体策略类:CNoDiscount、CFixedAmountDiscount、CPercentageDiscount。其中,CNoDiscount表示无折扣,直接返回原价;CFixedAmountDiscount应用固定金额折扣,从原价中减去指定金额;CPercentageDiscount则根据给定的百分比来计算折后价格。
为了管理这些策略,我们定义了购物车上下文类CShoppingCartContext。它持有一个指向当前折扣策略的指针,并提供了设置新策略和获取最终价格的方法。
最后,在main函数中,我们创建了购物车实例,动态切换了不同的折扣策略,并打印输出各自的折后价格。
cpp
#include <iostream>
using namespace std;
// 策略接口
class CDiscountStrategy
{
public:
virtual ~CDiscountStrategy() {}
virtual double Calculate(double price) const = 0;
};
// 具体策略类:无折扣
class CNoDiscount : public CDiscountStrategy
{
public:
double Calculate(double price) const override
{
return price;
}
};
// 具体策略类:固定金额折扣
class CFixedAmountDiscount : public CDiscountStrategy
{
public:
CFixedAmountDiscount(double amount) : m_discountAmount(amount) {}
double Calculate(double price) const override
{
return price - m_discountAmount;
}
private:
double m_discountAmount;
};
// 具体策略类:百分比折扣
class CPercentageDiscount : public CDiscountStrategy
{
public:
CPercentageDiscount(double rate) : m_discountRate(rate) {}
double Calculate(double price) const override
{
return price * (1 - m_discountRate);
}
private:
double m_discountRate;
};
// 购物车上下文类
class CShoppingCartContext
{
public:
CShoppingCartContext(CDiscountStrategy* pStrategy) : m_pStrategy(pStrategy) {}
~CShoppingCartContext()
{
delete m_pStrategy;
m_pStrategy = NULL;
}
void SetStrategy(CDiscountStrategy* pStrategy)
{
if (pStrategy != m_pStrategy)
{
delete m_pStrategy;
m_pStrategy = pStrategy;
}
}
double GetFinalPrice(double originalPrice) const
{
if (m_pStrategy == NULL)
{
return 0.0;
}
return m_pStrategy->Calculate(originalPrice);
}
private:
CDiscountStrategy* m_pStrategy;
};
int main()
{
// 创建购物车购物车,并设置初始折扣策略
CShoppingCartContext cart(new CNoDiscount());
double price = 100.0;
cout << "Original Price: " << price << endl;
cout << "Final Price with No Discount: " << cart.GetFinalPrice(price) << endl;
// 更换为固定金额折扣策略
cart.SetStrategy(new CFixedAmountDiscount(10));
cout << "Final Price with Fixed Amount Discount: " << cart.GetFinalPrice(price) << endl;
// 更换为百分比折扣策略
cart.SetStrategy(new CPercentageDiscount(0.2));
cout << "Final Price with Percentage Discount: " << cart.GetFinalPrice(price) << endl;
return 0;
}
总结
策略模式可有效分离变化点,它将算法的变更从使用该算法的地方分离出来,提高了代码的灵活性和可扩展性。新增加一个策略时,不需要修改现有代码,只需要添加新的具体策略类即可。另外,每个具体策略都是一个独立的类,因此可以很容易地对其进行单元测试。
但由于每种策略都需要定义一个具体的类,可能会导致类的数量增多,增加了系统的复杂度。除此之外,为了选择合适的策略,客户端通常需要知道有哪些可用的策略以及它们的区别,这可能违反了依赖倒置原则。