前言
关于策略模式我有以下几个问题想问:
- 什么是策略模式?
- 为什么需要策略模式(也就是什么情况下使用策略模式)?
- 策略模式的优点是什么?
- 策略模式的缺点是什么?
策略模式期望策略在运行时被加载,而不是编译时加载。
模式定义
在《设计模式》一书中,对策略模式做了如下释义:
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户程序而变化。
针对GoF这句话,我们需要把握两个点。第一个是“一系列算法”,也就是存在多个算法。那么这些算法需要怎么样呢?——也就是我们需要把握的第二点:“算法可独立于客户端”。
通俗地说:算法可以增删改,但客户端代码不应该有变动。
if...else if...
《晋书·阮籍传》中说:“及嵇喜来吊,籍作白眼,喜不择而退。喜弟康闻之,乃筴酒挟琴造焉,籍大悦,乃见青眼。”大概意思是阮籍对不喜欢的人会白眼视之,但对待喜欢的人则青眼相待。
在实际业务中,针对不同状况采用不同处理方式很常见。比方现在需要一个爬虫程序,其目的是抓取某人在某些平台上的历史记录(淘宝购买记录,爱奇艺观看记录,知乎回答记录),我们可以通过这些“记录”数据多维度的分析一个人——怎么使用数据当然是后话了。
既然是历史记录,那么需要登录之后才能访问这些数据。但不同网站模拟登录的方式不同,所以我们使用不同的处理方式。这样需求的骨架如何设计?最先跑进脑海的自然是if...else if...组合。
enum WhichSite {
TAO_BAO,
AI_QIYI,
ZHI_HU
};
class Spider {
private:
WhichSite whichSite;
public:
Spider(WhichSite whichSite): whichSite(whichSite) {}
bool auto_login();
};
bool Spider::auto_login()
{
if (whichSite == TAO_BAO) {
... // 淘宝自动登录
}
else if (whichSite == AI_QIYI) {
... // 爱奇艺自动登录
}
else if (whichSite == ZHI_HU) {
... // 知乎自动登录
}
}
后来我们发现,淘宝、爱奇艺、知乎三家平台的数据不足够我们准确分析一个人,在购物上我们还需要京东、当当、拼多多等,在观影上需要优酷、腾讯视频……也就是说,我们又要对更多网站写模拟登陆。上述代码跟随业务做调整,呈现如下:
bool Spider::auto_login()
{
...
else if (whichSite == ZHI_HU) {
... // 知乎自动登录
}
else if (whichSite == DANG_DANG) {
... // 当当自动登录
}
else if (whichSite == YOU_KU) {
... // 优酷自动登录
}
...
}
只要增加网站,就必须修改Spider::auto_login()函数,这不符合开闭原则。爬虫程序支持哪些网站的自动登录,对Spider::auto_login()来说应该是透明的,它的义务仅仅是去执行”自动登录“这个动作。
策略模式
回忆开头提到的策略模式的两个要点:1. 一系列算法 2. 算法独立于客户端。
多个自动登录可看作一系列算法,调用自动登陆的Spider::auto_login()可视作客户端。因此,我们可以用策略模式优化之前的if...else if...结构。
首先,为管理方便起见,我们应该让各个网站的自动登陆继承自同一个抽象基类:
class LoginStrategy {
public:
virtual bool login_logic() = 0;
virtual ~LoginStrategy() {}
};
class TABAO: public LoginStrategy {
public:
virtual bool login_logic() { ... /* 登陆逻辑 */ }
};
class AIQIYI: public LoginStrategy {
public:
virtual bool login_logic() { ... /* 登陆逻辑 */ }
};
... // 省略其他网站
相应的,爬虫程序(Spider类)设计如下:
class Spider {
private:
LoginStrategy* loginStrategy;
public:
Spider(LoginStrategy* ls): loginStrategy(ls) {}
bool auto_login();
};
bool Spider::auto_login()
{
loginStrategy->login_logic(); // 使用登录逻辑
}
使用方式:
int main()
{
AIQIYI* aiyiqi = new AIQIYI(); // 使用一种登录逻辑
Spider* spider = new Spider(aiyiqi); // 对客户端装载这种逻辑
spider->auto_login(); // 登录
TAOBAO* taobo = new TAOBAO(); // 也可以轻易的更换一种登录逻辑
Spider* spider = new Spider(taobo);
spider->auto_login();
}
此后无论是增加还是删除一个网站,都不需要改动客户端Spider,符合关闭原则。这便是策略模式带来的好处。
关系梳理
条件语句:
策略模式:
模式结构
策略模式包含如下角色:
- Context: 环境类(对应Spider)
- Strategy: 抽象策略类(对应LoginStrategy)
- ConcreteStrategy: 具体策略类 (对应TAOBO、AIQIYI……)
总结
策略模式优点:
- 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为;
- 策略模式提供了管理相关的算法族的办法;
- 使用策略模式可以避免使用多重条件转移语句(if...else if...)。
策略模式缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类(也就是我们使用auto_login()之前,需要自行选择登陆逻辑
Spider* spider = new Spider(aiyiqi)
,或者taobao,或者zhihu,等等)。
消除条件判断语句是一个解耦的过程。含有许多条件判断语句的代码通常都需要Strategy模式。
感谢
- 参考李建忠老师的《C++模式设计》
- 参考《Graphic Design Patterns》中的策略模式篇
还不快抢沙发