前言

尊重原创,本系列文本解析部分主要基于Candidate for Master’s Degree School of Computer Wuhan University的K_Eckel([email protected])《设计模式精解-GoF 23 种设计模式解析附 C++实现源码》。为了避免重造轮子,本系列博文对源码在ubuntu16.04下做了验证并上传到了gitee,再次感谢。

如有问题,可邮件联系我([email protected])并共同探讨解决方案。

目录

创建型模式(Creating Pattern)

Factory 模式 | AbstactFactory 模式 | Singleton 模式 | Builder 模式 | Prototype 模式

结构型模式(Structrual Pattern)

Bridge 模式 | Adapter 模式 | Decorator 模式 | Composite 模式 | Flyweight 模式 | Facade 模式 | Proxy 模式

行为型模式(Behavioral Pattern)

Template 模式 | Strategy 模式 | State 模式 | Observer 模式 | Memento 模式 | Mediator 模式 | Command 模式 | Visitor 模式 | Iterator 模式 | Interpreter 模式 | Chain of Responsibility 模式

Strategy 模式简介:

  在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
  在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
  比如旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

问题:

  Strategy模式和Template模式要解决的问题是相同(类似)的,都是为了给业务逻辑(算法)具体实现和抽象接口之间的解耦。Strategy模式将逻辑(算法)封装到一个类(Context)里面,通过组合的方式将具体算法的实现在组合对象中实现,再通过委托的方式将抽象接口的实现委托给组合对象实现。State模式也有类似的功能,他们之间的区别将在讨论中给出。

模式选择

Strategy 模式典型的结构图为:
Strategy Pattern
图 1:Strategy 模式结构示意图 1

  这里的关键就是将算法的逻辑抽象接口(DoAction)封装到一个类中(Context),再通过委托的方式将具体的算法实现委托给具体的Strategy类来实现(ConcreteStrategeA 类)。

实现

完整代码示例(code)

   Strategy模式实现很简单,这里为了方便初学者的学习和参考,将给出完整的实现代码(所有代码采用 C++实现,并在 Visual Studio Code,Version: 1.36.1 下测试运行)。

源码gitee地址:点击这里

代码目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PS C:\Users\guopz\Desktop\GiteeBlog\designpatternsbycpipei\behavioralPattern\Strategy> tree /F    
卷 Windows 的文件夹 PATH 列表
卷序列号为 F0C5-AFA6
C:.
├─include
│ Context.h
│ Strategy.h

└─src
a.out
Context.cpp
main.cpp
Strategy.cpp

PS C:\Users\guopz\Desktop\GiteeBlog\designpatternsbycpipei\behavioralPattern\Strategy>

Context.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//Context.h

#ifndef _CONTEXT_H_
#define _CONTEXT_H_

class Strategy;
/**
* 这个类是 Strategy 模式的关键,也是 Strategy
* 模式和 Template 模式的根本区别所在。
*
* Strategy 通过“组合”(委托)方式实现算法
* (实现)的异构,而 Template 模式则采取的
* 是继承的方式
*
* 这两个模式的区别也是继承和组合两种实
* 现接口重用的方式的区别
*
* */

class Context
{
private:
Strategy* _stg;

public:
Context(Strategy* stg);
~Context();
void DoAction();
};

#endif //~_CONTEXT_H_

Strategy.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Strategy.h

#ifndef _STRATEGY_H_
#define _STRATEGY_H_

class Strategy {
private:
/* data */
public:
Strategy(/* args */);
virtual ~Strategy();
virtual void AlgrithmInterface() = 0;
};

class ConcreteStrategyA : public Strategy {
private:
/* data */
public:
ConcreteStrategyA(/* args */);
virtual ~ConcreteStrategyA();
void AlgrithmInterface();
};

class ConcreteStrategyB : public Strategy {
private:
/* data */
public:
ConcreteStrategyB(/* args */);
virtual ~ConcreteStrategyB();
void AlgrithmInterface();
};

#endif //~_STRATEGY_H_

Context.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Context.cpp

#include "../include/Context.h"
#include "../include/Strategy.h"
#include <iostream>

using namespace std;

Context::Context(Strategy* stg)
{
_stg = stg;
}
Context::~Context()
{
if (!_stg) delete _stg;
}
void Context::DoAction(){
_stg->AlgrithmInterface();
}

Strategy.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Strategy.cpp

#include "../include/Strategy.h"
#include <iostream>

using namespace std;

Strategy::Strategy(/* args */) {}
Strategy::~Strategy() { printf("~Strategy.... \n"); }
void Strategy::AlgrithmInterface() {}

ConcreteStrategyA::ConcreteStrategyA(/* args */) {}
ConcreteStrategyA::~ConcreteStrategyA() { printf("~ConcreteStrategyA..... \n"); }
void ConcreteStrategyA::AlgrithmInterface() {
printf("test ConcreteStrategyA......AlgrithmInterface \n");
}

ConcreteStrategyB::ConcreteStrategyB(/* args */) {}
ConcreteStrategyB::~ConcreteStrategyB() { printf("~ConcreteStrategyB..... \n"); }
void ConcreteStrategyB::AlgrithmInterface() {
printf("test ConcreteStrategyB......AlgrithmInterface \n");
}

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// main.cpp

#include <iostream>
#include "../include/Context.h"
#include "../include/Strategy.h"

using namespace std;

int main(int argc, char* argv[]) {
Strategy* ps;
ps = new ConcreteStrategyA();
Context* pc = new Context(ps);
pc->DoAction();
if (NULL != pc) delete pc;

return 0;
}

代码说明

  Strategy模式的代码很直观,关键是将算法的逻辑封装到一个类中。

编译运行结果:

1
2
3
4
PS C:\Users\guopz\Desktop\GiteeBlog\designpatternsbycpipei\behavioralPattern\Strategy\src> g++ *.cpp -std=c++11
PS C:\Users\guopz\Desktop\GiteeBlog\designpatternsbycpipei\behavioralPattern\Strategy\src> .\a.exe
test ConcreteStrategyA......AlgrithmInterface
PS C:\Users\guopz\Desktop\GiteeBlog\designpatternsbycpipei\behavioralPattern\Strategy\src>

讨论

  可以看到Strategy模式和Template模式解决了类似的问题,也正如在Template模式中分析的,Strategy模式和Template模式实际是实现一个抽象接口的两种方式:继承和组合之间的区别。要实现一个抽象接口,继承是一种方式:我们将抽象接口声明在基类中,将具体的实现放在具体子类中。组合(委托)是另外一种方式:我们将接口的实现放在被组合对象中,将抽象接口放在组合类中。这两种方式各有优缺点,先列出来:

  • 继承:
    • 优点
      • 易于修改和扩展那些被复用的实现。
    • 缺点
      • 破坏了封装性,继承中父类的实现细节暴露给子类了;
      • “白盒”复用,原因在1)中;
      • 当父类的实现更改时,其所有子类将不得不随之改变
      • 从父类继承而来的实现在运行期间不能改变(编译期间就已经确定了)。
  • 组合
    • 优点
      • “黑盒”复用,因为被包含对象的内部细节对外是不可见的;
      • 封装性好,原因为1);
      • 实现和抽象的依赖性很小(组合对象和被组合对象之间的依赖性小);
      • 可以在运行期间动态定义实现(通过一个指向相同类型的指针,典型的是抽象基类的指针)。
    • 缺点
      • 系统中对象过多。

   从上面对比中我们可以看出,组合相比继承可以取得更好的效果,因此在面向对象的设计中的有一条很重要的原则就是:优先使用(对象)组合,而非(类)继承(FavorComposition Over Inheritance)。
   实际上,继承是一种强制性很强的方式,因此也使得基类和具体子类之间的耦合性很强。例如在Template模式中在ConcreteClass1中定义的原语操作别的类是不能够直接复用(除非你继承自AbstractClass,具体分析请参看Template模式文档)。而组合(委托)的方式则有很小的耦合性,实现(具体实现)和接口(抽象接口)之间的依赖性很小,例如在本实现中,ConcreteStrategyA的具体实现操作很容易被别的类复用,例如我们要定义另一个ContextAnotherContext,只要组合一个指向Strategy的指针就可以很容易地复用ConcreteStrategyA的实现了。
   我们在Bridge模式的问题和Bridge模式的分析中,正是说明了继承和组合之间的区别。请参看相应模式解析。
   另外Strategy模式很State模式也有相似之处,但是State模式注重的对象在不同的状态下不同的操作。两者之间的区别就是State模式中具体实现类中有一个指向Context的引用,而Strategy模式则没有。具体分析请参看相应的State模式分析中。