策略模式(Strategy)是一种行为型设计模式,它允许你在运行时选择算法的行为。
策略模式有三个组件:
- 策略接口:定义了策略类必须实现的方法,它通常是以接口或者抽象类的方式存在
- 具体策略类:实现了策略接口,提供各种算法。
- 上下文类:将策略接口作为一个成员变量,运行时通过设置不同的策略类切换不同的算法
举例:
现在我们有一笔金额,要按照不同国家的汇率进行计算,我们可以这样实现:
// 各国汇率
enum Rate
{
CN_Rate, // 中国汇率
FR_Rate, // 英国汇率
US_Rate // 没过汇率
};
// 运行
class CalculateRate
{
public:
CalculateRate()
{
}
~CalculateRate(){}
void SetRate(Rate _rate)
{
rate_ = _rate;
}
double Calculate(const double& _data)
{
if (rate_ == CN_Rate)
{
// TODO 按照中国汇率进行计算
std::cout << "中国汇率" << std::endl;
}
else if (rate_ == FR_Rate)
{
// TODO 按照法国汇率进行计算
std::cout << "法国汇率" << std::endl;
}
else if (rate_ == US_Rate)
{
// TODO 按照美国汇率进行计算
std::cout << "美国汇率" << std::endl;
}
return 0.0;
}
private:
Rate rate_;
};
我们定义了一个枚举,里面是各个国家的类型,当我们要按照美国的汇率进行计算时,我们可以通过SetRate方法设置国家类型,再调用Calculate方法进行计算,在这个方法里我们会判断当前是哪个国家,执行对应的算法。
其实这个例子已经实现了我们现在的需求,但是项目开发过程中需求是不断的变化。比如现在我们要加一个日本的汇率,我们应该在枚举里添加一个日本,在Calculate方法里继续else if执行日本汇率对应的算法。
这样其实违背了项目开发原则中的开放封闭原则。
所谓开放封闭原则就是对扩展开放、对修改封闭,换句话说我们应该通过添加新的代码来实现需求,而不是修改原有的代码。
现在我们用策略模式来完成这个需求,按照上述策略模式的三个组件来编写代码:策略接口、具体的策略类、上下文类
// 定义策略接口
class Strategy
{
public:
virtual ~Strategy(){}
virtual double CalculateRate(const double& _data) = 0;
};
// 根据中国汇率计算的策略类
class CNRateStrategy
: public Strategy
{
public:
virtual double CalculateRate(const double& _data) override
{
// TODO 按照中国汇率进行计算
std::cout << "中国汇率" << std::endl;
return 0.0;
}
};
// 按照法国汇率计算的策略类
class FRRateStrategy
: public Strategy
{
public:
virtual double CalculateRate(const double& _data) override
{
// TODO 按照法国汇率进行计算
std::cout << "法国汇率" << std::endl;
return 0.0;
}
};
// 按照美国汇率计算的策略类
class USRateStrategy
: public Strategy
{
public:
virtual double CalculateRate(const double& _data) override
{
// TODO 按照美国汇率进行计算
std::cout << "美国汇率" << std::endl;
return 0.0;
}
};
// 上下文类
class Context
{
public:
Context()
{}
void SetRate(std::shared_ptr<Strategy> _strategy)
{
strategy_ = _strategy;
}
void Calculate(const double& _value)
{
if(strategy_)
strategy_->CalculateRate(_value);
}
private:
std::shared_ptr<Strategy> strategy_;
};
我们通过Context类里的SetRate方法设置具体的策略类,通过Calculate方法执行算法。
void TestStrategy()
{
Context context;
context.SetRate(std::make_shared<CNRateStrategy>());
context.Calculate(10.0);
context.SetRate(std::make_shared<FRRateStrategy>());
context.Calculate(10.0);
context.SetRate(std::make_shared<USRateStrategy>());
context.Calculate(10.0);
}
int main()
{
// 策略模式用法
TestStrategy();
system("pause");
return 0;
}
现在我们要添加一个日本的汇率,我们就可以仿照美国、中国、法国的策略类的写法写一个日本的策略类,在运行的时候设置日本的策略类,这样它就会执行日本的算法。
我们可以发现使用策略模式就不会违背开放封闭原则,因为我们是添加代码而不是修改原有代码。
可能会有人说第一种写法不是也是添加代码吗,我们这里说的“添加”是在二进制的角度,所谓的添加是指添加之后编译添加后的代码,而不是把原有代码重新编译一遍。
策略模式遵循的设计原则:
- 单一职责原则:策略模式将每个算法或策略都封装在独立的类中,使得每个类只负责一个算法或策略,符合单一职责原则,使得代码更加清晰、可维护和可扩展。
- 开闭原则:策略模式通过定义接口或抽象基类,针对接口编程,使得可以在无需改变现有代码的情况下添加策略或算法。
- 依赖倒置原则:策略模式使用抽象基类定义接口,使得客户端和具体实现不互相依赖,它们都依赖抽象基类。
优点:
- 算法的独立性:每个算法或策略都被封装在独立的类中,使得它们可以独立开发、测试和维护,提高了代码的可读性和可维护性。
- 可扩展性:通过使用抽象基类定义接口,可以在不修改现有代码的基础上灵活添加算法和策略。
- 可替换性:策略模式使得算法或策略可以在运行时动态切换,调用者不需要关心具体实现细节。
缺点:
- 增加了系统的复杂性:如果系统中的算法或策略数量过大,会增加类的数量,提高系统的复杂性。
总结:
- Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换
- Stratergy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。
- 如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销