设计模式与行为型模式
设计模式是在长时间实践中,开发人员总结出的优秀架构与解决方案。学习设计模式将有助于经验不足的开发人员在实际开发中,灵活地运用面向对象特性,并能够快速构建不同场景下的程序框架,写出优质代码。
设计模式分类
- 行为型模式:关注对象行为功能上的抽象,从而提升对象在行为功能上的可拓展性,能以最少的代码变动完成功能的增减。
- 结构型模式:关注对象之间结构关系上的抽象,从而提升对象结构的可维护性、代码的健壮性,能在结构层面上尽可能的解耦合。
- 创建型模式
本章内容主要探讨设计模式中的行为型模式。
行为型模式分类
本章介绍三种行为型模式。
模版方法 Template Method
模版方法是一种针对接口编程的设计。
模版方法的思想是,基类是一个模板(也可以称作原型)。基类体现的是“抽象概念”,里面定义若干个纯虚函数,这些函数提供了这个类的“接口”。
比如,如果一个操作有 operation1
、operation2
两个步骤,我们可以定义一个基类:
| class AbstractClass {
public:
virtual void Operation1() = 0;
virtual void Operation2() = 0;
};
|
这个操作可能有很多个版本。每个版本的实现细节不同。比如,AbstractClass
可能是“监视计算机节点的负载状态”的过程,Operation1
是“得到总内存”,Operation2
是“得到已占用内存”。而这两种操作在不同的环境下,比如 Win32 和 Win64 下可能是不同的。这些不同的细节则由子类负责实现。
在使用时,抽象类的算法骨架提供了大致方法,再由这个方法来根据需要调用具体类的实现细节。比如:
| class ConcreteClassA: public AbstractClass {
void Operation1() {
cout << "Do Operation 1 of A" << endl;
}
void Operation2() {
cout << "Do Operation 2 of A" << endl;
}
};
|
这样当我们要拓展一种新的实现类时,重新对基类进行继承与实现即可,无需对已有的实现类进行修改。
由模板实现多态
现在我们有了很多继承自同一个抽象类的实现类,该怎么实现多态呢?
我们知道,模板多态是依赖于指针和引用的。编译器能够通过指针和引用判断实际指向的类型,并且调用实际类型里面 override 了的虚函数。所以在使用模板模式的时候,常常创建基类指针来调用实现类的函数。
例:
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
35
36 | #include <iostream>
using namespace std;
class AbstractClass {
public:
virtual void Operation1() = 0;
virtual void Operation2() = 0;
};
class ConcreteClassA: public AbstractClass {
public:
void Operation1() {
cout << "Do Operation 1 of A" << endl;
}
void Operation2() {
cout << "Do Operation 2 of A" << endl;
}
};
class ConcreteClassB: public AbstractClass {
public:
void Operation1() {
cout << "Do Operation 1 of B" << endl;
}
void Operation2() {
cout << "Do Operation 2 of B" << endl;
}
};
int main() {
AbstractClass *abstract;
abstract = new ConcreteClassB();
abstract->Operation1();
abstract->Operation2();
return 0;
}
|
策略模式 Strategy Method
模板模式中,每个实现类里面可能有多个功能(比如 Operation1
、Operation2
等)。可能有两个类,它们各有 N 个功能,其中只有一个的实现有区别。在模板模式下,我们却必须实现两个类。这就很麻烦。
为了避免这种情况,使用策略模式。
策略模式:定义一系列算法并加以封装,使得这些算法可以互相替换。这样,一种算法就不需要依附于某个实现类了,而是自成一类。
具体而言:
-
每一种行为各自有方法虚基类 A
、B
-
每一个方法基类 A
有若干具体的方法 A1
、A2
、A3
,每一种都会继承 A
-
所有对象具有一个对象基类 O
,对象基类 O
含有所有的方法基类指针 A*
、B*
,从而实现多态。注意到对象基类如果仅含有方法基类指针,实际上没法调用方法基类的方法,故而还需要调用接口
-
每种对象是一个具体的对象类 O1
、O2
、O3
,每种都会继承对象类 O
,同时让方法基类指针 A*
、B*
具体指向方法派生类上 Ax
、By
例子
比如下面这个例子:有三种鸭子:MallarDuck(绿头鸭)、RubberDuck(橡皮鸭)、DecoyDuck(诱饵鸭子)
- MallarDuck 会 quack 地叫,会飞
- RubberDuck 会 queak 地叫,不会飞
- DecoyDuck 不会叫,不会飞
发现所有鸭子都有飞、叫两种功能,但是各自的实现不太一样。我们不在每种鸭子里分别实现,而是在基类鸭子里储存这两种功能的基类指针,然后让鸭子的实现类指向对应的两种功能的实现类。
实现如下:
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 | #include <iostream>
class FlyBehavior {
public:
virtual ~FlyBehavior() { };
virtual void fly() = 0;
};
class QuackBehavior {
public:
virtual ~QuackBehavior() { };
virtual void quack() = 0;
};
class Duck{
public:
Duck(FlyBehavior* p_FlyBehavior, QuackBehavior* p_QuackBehavior) {
pFlyBehavior = p_FlyBehavior;
pQuackBehavior = p_QuackBehavior;
}
virtual ~Duck() { };
virtual void display() { };
void performFly() {
pFlyBehavior->fly();
}
void performQuack() {
pQuackBehavior->quack();
}
private:
FlyBehavior* pFlyBehavior;
QuackBehavior* pQuackBehavior;
};
class FlyWithWings: public FlyBehavior {
public:
void fly() {
std::cout << "I'm flying!!" << std::endl;
}
};
class FlyNoWay: public FlyBehavior {
public:
void fly() {
std::cout << "I can't fly" << std::endl;
}
};
class Quack: public QuackBehavior {
public:
void quack() {
std::cout << "Quack" << std::endl;
}
};
class MuteQuack: public QuackBehavior {
public:
void quack() {
std::cout << "<< Slience >>" << std::endl;
}
};
class Squeak: public QuackBehavior {
public:
void quack() {
std::cout << "Squeak" << std::endl;
}
};
class MallardDuck: public Duck { // 继承了对象基类,就已经有了方法基类指针
public:
MallardDuck(
FlyBehavior* fly_behavior = new FlyWithWings(), // 构造函数含有缺省值
QuackBehavior* quack_behavior = new Quack())
: Duck(fly_behavior, quack_behavior) { } // 绿头鸭的构造函数,飞行和叫声的基类指针分别指向了对应的实现类
void display() {
std::cout << "I'm a real Mallard duck" << std::endl;
}
};
class RubberDuck: public Duck {
public:
RubberDuck(
FlyBehavior* fly_behavior = new FlyNoWay(),
QuackBehavior* quack_behavior = new Squeak())
: Duck(fly_behavior, quack_behavior) { }
void display() {
std::cout << "I'm a Rubber duck" << std::endl;
}
};
class DecoyDuck: public Duck {
public:
DecoyDuck(
FlyBehavior* fly_behavior = new FlyNoWay(),
QuackBehavior* quack_behavior = new MuteQuack())
: Duck(fly_behavior, quack_behavior) { }
void display() {
std::cout << "I'm just a Decoy duck" << std::endl;
}
};
int main() {
Duck* mallard = new MallardDuck();
mallard->display();
mallard->performFly();
mallard->performQuack();
Duck*rubber = new RubberDuck();
rubber->display();
rubber->performQuack();
return 0;
}
/*
output:
I'm a real Mallard duck
I'm flying!!
Quack
I'm a Rubber duck
Squeak
*/
|
从上面的例子可以看出,策略模式不过是更加多态化的模板模式罢了。在模板模式下,每个类(比如在上面这个例子里是每种鸭子)中的 Fly
、Quack
函数都在这个鸭子类里实现。而这里则是先定义了 Fly
、Quack
这两种动作的基类,即 QuackBehavior
、FlyBehavior
。并且在在基类鸭子里储存了这两种基类类型的指针。
然后,再通过继承关系实现了各种不同的 Fly
、Quack
的实现类,并且在鸭子的实现类里让 QuackBehavior
、FlyBehavior
的基类指针指向不同的 Fly
、Quack
的实现类。