情景描述
三国时期 , 诸葛亮气周瑜的过程中 , 有一件事 : 那就是周瑜赔了夫人又折兵这件事 . 在刘备去东吴招亲之前 , 特授以伴郎赵云三个锦囊 , 这三个锦囊分别是 : 找乔国老帮忙 , 求吴国太放行以及孙夫人断后.在这个场景中 , 三个妙计都有了 , 还缺两个配角 : 第一 , 承装妙计的锦囊 ; 第二 , 执行妙计的人.
实现代码如下 :
#include<iostream>
#include<string>
//#include<vld.h>
using namespace std;
//策略模式
#if 0
//妙计接口
class IStrategy
{
public:
virtual void operate() = 0;
};
//乔国老开后门
class BackDoor : public IStrategy
{
public:
void operate()
{
cout<<"找乔国老帮忙 , 让吴国太给孙尚香施加压力"<<endl;
}
};
//吴国太开绿灯
class GivenGreeLight : public IStrategy
{
public:
void operate()
{
cout<<"求吴国太开绿灯 , 放行!"<<endl;
}
};
//孙夫人断后
class BlockEney : public IStrategy
{
public:
void operate()
{
cout<<"孙夫人断后 , 挡住追兵"<<endl;
}
};
//锦囊
class Context
{
public:
//构造函数 , 你要使用哪个妙计
Context(IStrategy* str = nullptr)
{
strategy = str;
}
//使用计谋了 , 看我出招了
void operate()
{
strategy->operate();
}
~Context()
{
if(strategy != nullptr)
delete strategy;
}
private:
IStrategy* strategy;
};
//使用计谋
//赵云出场 , 他根据诸葛亮的交代 ,依次拆开锦囊
int main()
{
Context* context;
//刚刚到吴国的时候拆第一个
cout<<"-----------刚刚到吴国的时候拆第一个----------"<<endl;
//拿到妙计
context = new Context(new BackDoor());
//开始执行
context->operate();
delete context;
//刘备乐不思蜀了 , 拆第二个了
cout<<"-----------刘备乐不思蜀了 , 拆第二个----------"<<endl;
context = new Context(new GivenGreeLight());
//执行第二个锦囊
context->operate();
delete context;
//孙权的小兵追来了 , 咋办? 拆第三个
cout<<"-----------孙权的小兵追来了 , 咋办? 拆第三个----------"<<endl;
context = new Context(new BlockEney());
//孙夫人退兵
context->operate();
delete context;
return 0;
}
#endif
运行结果 :
策略模式的定义
定义 : 定义一组算法,将每个算法都封装起来,并且使他们之间可以互换 .
策略模式的通用类图 :
策略模式的结构 :
- Context封装角色
也叫上下文,对策略进行二次封装,目的是避免高层模块对策略的直接调用 . - Strategy抽象策略角色
通常情况下为一个接口,当各个实现类中存在着重复的逻辑时,则使用抽象类来封装这部分公共的代码,此时,策略模式看上去更像是模版方法模式 . - ContextStrategy具体策略
具体策略角色通常由一组封装了算法的类来担任,这些类之间可以根据需要自由替换 .
策略模式的应用
策略模式的优点
- 算法可以自由切换
这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员, 通过封装角色对其进行封装,保证对外提供“可自由切换”的策略 . - 避免使用多重条件判断
如果没有策略模式,我们想想看会是什么样子?一个策略家族有5个策略算法,一会要使用A策略,一会要使用B策略,怎么设计呢?使用多重的条件语句?多重条件语句不易维护,而且出错的概率大大增强 . 使用策略模式后,可以由其他模块决定采用何种策略,策略家族对外提供的访问接口就是封装类,简化了操作,同时避免了条件语句判断 . - 扩展性良好
这甚至都不用说是它的优点,因为它太明显了 . 在现有的系统中增加一个策略太容易了,只要实现接口就可以了,其他都不用修改,类似于一个可反复拆卸的插件,这大大地符合了OCP原则 .
策略模式的缺点
- 策略类数量增多
每一个策略都是一个类,复用的可能性很小,类数量增多 . - 所有的策略类都需要对外暴露
上层模块必须知道有哪些策略,然后才能决定使用哪一个 策略,这与迪米特法则是相违背的,我只是想使用了一个策略,我凭什么就要了解这个策略呢?那要你的封装类还有什么意义?这是原装策略模式的个缺点, 幸运的是,我们可以使用其他模式来修正这个缺陷,如工厂方法模式、代理模式或享元模式 .
策略模式的使用场景
- 多个类只有在算法或行为上稍有不同的场景 .
- 算法需要自由切换的场景 .
例如,算法的选择是由使用者决定的,或者算法始终在进化,特别是一些站在技术前沿的行业,连业务专家都无法给你保证这样的系统规则能够存在多长时间,在这种情况下策略模式是你最好的助手 . - 需要屏蔽算法规则的场景 .
现在的科技发展得很快,人脑的记忆是有限的(就目前来说是有限的),太多的算法你只要知道一一个名字就可以了,传递相关的数字进来,反馈一个运算结果,万事大吉 .
策略模式的注意事项
如果系统中的一个策略家族的具体策略数量超过4个,则需要考虑使用混合模式,解决策略类膨胀和对外暴露的问题,否则日后的系统维护就会成为一个烫手山芋,谁都不想接 .
总结
它与模版方法模式的区别在于:在模版方法模式中,调用算法的主体在抽象的父类中,而在策略模式中,调用算法的主体则是封装到了封装类Context中,抽象策略Strategy一般是一个接口,目的只是为了定义规范,里面一般不包含逻辑 . 其实,这只是通用实现,而在实际编程中,因为各个具体策略实现类之间难免存在一些相同的逻辑,为了避免重复的代码,我们常常使用抽象类来担任Strategy的角色,在里面封装公共的代码,因此,在很多应用的场景中,在策略模式中一般会看到模版方法模式的影子 .
参考书籍 :
<<设计模式之禅 第二版>>
<<设计模式>>