策略模式(Strategy)
动机(Motivation)
在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候不使用的算法也是一个性能负担。
如何在运行时根据需要透明的改变对象的算法?将算法与对象本身解耦,从而避免上述问题?
模式定义
定义一系列算法,把他们一个个的封装起来,并且使他们可互相替换(变化)。该模式使得算法可以独立于他用他的客户程序(稳定)而变化(扩展,子类化)。 ——《设计模式》GoF
实例
现在有一个动物叫声的类,用于获取各种动物的叫声,通过传入一个值判断是哪种动物
enum Animal
{
CAT = 1,
DOG = 2,
SHEEP = 3
};
class Animal_sounds
{
void sounds(int index)
{
if (index == CAT) {
//猫叫....
}
else if (index == DOG) {
//狗叫....
}
else if (index == SHEEP) {
//羊叫....
}
}
};
这种if-else写法,如果出现了某些改变,比如多加一个动物,那就需要改变主体代码,例如多加一个马的叫声,那就需要在枚举里加入马的枚举值,然后对if-else再加一层循环
enum Animal
{
CAT = 1,
DOG = 2,
SHEEP = 3,
HORSE = 4 //改变
};
class Animal_sounds
{
private:
Animal index;
public:
void sounds()
{
if (index == CAT) {
//猫叫....
}
else if (index == DOG) {
//狗叫....
}
else if (index == SHEEP) {
//羊叫....
}
else if (index == HORSE) { //改变
//马叫....
}
}
};
如果这样做的话,每次改变都需要不停的修改代码,然而在实际的工程之中,频繁的嵌套if-else,可能会造成上层if出现bug。此时看到很多if-else就应该想到Strategy,下边是Strategy的修改版本
class Strategy_sound //虚基类
{
public:
virtual void sounds() = 0;
virtual ~Strategy_sound(){}
};
class Cat_sound :public Strategy_sound
{
public:
virtual void sounds() {
//猫叫....
cout << "miao" << endl;
}
};
class Dog_sound :public Strategy_sound
{
public:
virtual void sounds() {
//狗叫....
}
};
class Animal_sounds
{
private:
Strategy_sound* strategy;//多态指针
public:
Animal_sounds(StrategyFactory* strategyFactory) { //此处用到工厂模式,后边会讲到
this->strategy = strategyFactory->NewStrategy();
}
void get_sounds()
{
this->strategy->sounds();//多态调用
}
};
此时用到Strategy,如果想要多加一个动物的叫声,是不改变代码主体框架的,只需要新实现一个类,利用多态调用,即可完成if-else所表达的功能,符合开闭原则(对更改关闭,对扩展开放)
看到if-else就应该想一想是否需要Strategy模式。如果if-else是绝对不变的,那就没必要用Strategy,例如一周只有七天,或者性别男,女。
优缺点
优点上文也提到了,就是可以大幅度降低if else的各种逻辑嵌套,提升代码可读性。
缺点也很明显,对于一个策略就要增加一个类,策略多了类也就多了。
要点总结
Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便的根据需要在各个算法之间进行切换。
Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。
如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。