Strategy策略模式

设计模式 2019-03-03 3587 字 3 浏览 点赞

前言

关于策略模式我有以下几个问题想问:

  • 什么是策略模式?
  • 为什么需要策略模式(也就是什么情况下使用策略模式)?
  • 策略模式的优点是什么?
  • 策略模式的缺点是什么?

策略模式期望策略在运行时被加载,而不是编译时加载。

模式定义

在《设计模式》一书中,对策略模式做了如下释义:

定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户程序而变化。

针对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》中的策略模式


本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论