抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

CodingStudio

努力进步

引言

C++设计模式学习笔记


1 设计模式简介

  • 如何解决复杂性
    • 分解:分而治之,将大问题分为多个小问题,将复杂问题分解成多个简单问题
    • 抽象:由于不能掌握全部的复杂信息,选择忽略非本质细节,而去处理返回和理想化了的对象模型
  • 使用抽象的方法,代码的复用性得到了提高
    • 抽象使得可以使用通用的方法统一处理
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// 分解的实现方法
// Shape1.cpp
class Point{
public:
int x;
int y;
};

class Line{
public:
Point start;
Point end;

Line(const Point& start, const Point& end){
this->start = start;
this->end = end;
}
};

class Rect{
public:
Point leftUp;
int width;
int height;

Rect(const Point& leftUp, int width, int height){
this->leftUp = leftUp;
this->width = width;
this->height = height;
}

};

//增加
class Circle{
// ....
};

// MainForm1.cpp
class MainForm : public Form {
private:
Point p1;
Point p2;

vector<Line> lineVector;
vector<Rect> rectVector;
//改变
vector<Circle> circleVector;

public:
MainForm(){
//...
}
protected:

virtual void OnMouseDown(const MouseEventArgs& e);
virtual void OnMouseUp(const MouseEventArgs& e);
virtual void OnPaint(const PaintEventArgs& e);
};


void MainForm::OnMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;

//...
Form::OnMouseDown(e);
}

void MainForm::OnMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;

if (rdoLine.Checked){
Line line(p1, p2);
lineVector.push_back(line);
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
Rect rect(p1, width, height);
rectVector.push_back(rect);
}
//改变
else if (...){
//...
circleVector.push_back(circle);
}

//...
this->Refresh();

Form::OnMouseUp(e);
}

void MainForm::OnPaint(const PaintEventArgs& e){

//针对直线
for (int i = 0; i < lineVector.size(); i++){
e.Graphics.DrawLine(Pens.Red,
lineVector[i].start.x,
lineVector[i].start.y,
lineVector[i].end.x,
lineVector[i].end.y);
}

//针对矩形
for (int i = 0; i < rectVector.size(); i++){
e.Graphics.DrawRectangle(Pens.Red,
rectVector[i].leftUp,
rectVector[i].width,
rectVector[i].height);
}

//改变
//针对圆形
for (int i = 0; i < circleVector.size(); i++){
e.Graphics.DrawCircle(Pens.Red,circleVector[i]);
}

//...
Form::OnPaint(e);
}


// 抽象的实现方法
// Shape2.cpp
class Shape{
public:
virtual void Draw(const Graphics& g)=0;
virtual ~shape(){}
// 声明虚析构函数,保证多态后的子类可以正确的调用析构函数实现析构
}

class Point{
public:
int x;
int y;
}

class Line: public Shape{
public:
Point start;
Point end;

Line(const Point& start, const Point& end){
this->start = start;
this->end = end;
}

// 实现自己的Draw函数
virtual void Draw(const Graphics& g){
g.DrawLine(Pens.Red,start.x,start.y,end.x,end.y);
}
}

class Rect: public Shape{
public:
Point leftUp;
int width;
int height;

Rect(const Point& leftUp, int width, int height){
this -> leftUp = leftUp;
this -> width = width;
this -> height = height;
}

virtual void Draw(const Graphics& g){
g.DrawLine(Pens.Red, leftUp, width, height);
}
}

// 改变
class Circle : public Shape{
// ....
}

// MainForm2.cpp
class MainForm : public Form {
private:
Point p1;
Point p2;

//针对所有形状
vector<Shape*> shapeVector;
// 为了支持多态性,存放父类的指针

public:
MainForm(){
//...
}
protected:

virtual void OnMouseDown(const MouseEventArgs& e);
virtual void OnMouseUp(const MouseEventArgs& e);
virtual void OnPaint(const PaintEventArgs& e);
};


void MainForm::OnMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;

//...
Form::OnMouseDown(e);
}

void MainForm::OnMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;

if (rdoLine.Checked){
// 因为存放的是指针,故需要new一个对象
// 需要放一个堆对象的指针,而不是栈对象
// 对内存管理有要求,在合适的时候需要负责释放Vector中的对象
shapeVector.push_back(new Line(p1,p2));
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
shapeVector.push_back(new Rect(p1, width, height));
}
//改变
// 使用工厂设计模式后,变化可以消除
else if (...){
//...
shapeVector.push_back(circle);
}

//...
this->Refresh();

Form::OnMouseUp(e);
}

void MainForm::OnPaint(const PaintEventArgs& e){
//针对所有形状
for (int i = 0; i < shapeVector.size(); i++){

shapeVector[i]->Draw(e.Graphics); //多态调用,各负其责
}

//...
Form::OnPaint(e);
}

2 面向对象设计原则

  • 面向对象设计的最大优势:抵御变化
  • 认识面向对象(抽象思维层面)
    • 理解隔离机制:面向对象的构建方式更能适应软件的变化能将变化所带来的影响减为最小
    • 各司其职
      • 微观层面上面向对象方式强调各个类的责任
      • 由于需要变化导致的新增类型不应该影响原来类型的实现(各负其责)
    • 对象是什么
      • 语言实现层面:对象封装了代码和数据
      • 规格层面:可被使用的公共接口
      • 概念层面:是某种拥有责任的对象
  • 面向对象设计原则(8大设计原则)
    • 依赖倒置原则(DIP)
      • 高层模块(稳定)不应该依赖于低层模块(变化),两者都应该基于抽象(稳定)
      • 抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)
      • 需要实现隔离变化
    • 开放封闭原则(OCP)
      • 对扩展开放,对更改封闭
      • 类模块应该是可扩展的,但是不可修改
    • 单一职责原则(SRP)
      • 一个类应该仅有一个引起它变化的原因
      • 变化的方向隐含着类的责任
    • Liskov替换原则(LSP)
      • 子类必须能够替换它们的基类(IS-A)
      • 继承表达类型抽象
    • 接口隔离原则(ISP)
      • 不应该强迫客户程序依赖它们不用的方法
      • 接口应该小而完备
    • 优先使用对象组合,而不是类继承
      • 类继承通常为白箱复用,对象组合通常为黑箱复用
      • 继承在某种程序上破坏了封装性,子类父类耦合度高
      • 对象组合只要求被组合的对象具有良好定义的接口,耦合度低
    • 封装变化点
      • 使用封装来创建对象之间的分界层,让设计者可以在分界层一侧进行修改,而不会对另一侧产生不良影响,从而实现层次间的松耦合
    • 针对接口编程,而不是针对实现编程
      • 不将变量类型声明为某个特定的具体类,而是声明为某个接口
      • 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口
      • 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案
  • GOF-23模板分类
    • 从目的来看
      • 创建型模式:将对象的部分创建工作延迟到子类或其他对象,从而应对需求变化为对象创建时具体类型实现引来的冲击
      • 结构型模式:通过类继承或对象组合获得更灵活的结构,从而应对需求变化为对象的结构带来的冲击
      • 行为型模式:通过类继承或对象组合来划分类与对象间的职责,从而应对需求变化为多个交互的对象带来的冲击
    • 从范围来看
      • 类模式处理类与子类的静态关系(继承方案)
      • 对象模式处理对象间的动态关系(组合方案)
从封装变化角度对模式分类 - - -
组件协作 模板方法 接口隔离 门面模式
- 观察者模式 - 代理模式
- 策略模式 - 适配器
单一职责 装饰模式 - 中介器*
- 桥模式 状态变化 状态模式*
对象创建 工厂方法 - 备忘录
- 抽象工厂 数据结构 组合模式
- 原型模式 - 迭代器*
- 构建器* - 职责链*
对象性能 单件模式 行为变化 命令模式*
- 享元模式 - 访问器*
领域问题 解析器* *为C++中不常用的模式
  • 重构关键技法
    • 静态->动态
    • 早绑定->晚绑定
    • 继承->组合
    • 编译时依赖->运行时依赖
    • 紧耦合->松耦合

3 组件协作

  • 组件协作模式
    • 现代软件专业分工后是框架与应用程序的划分
    • 组件协作模式是通过晚期绑定来实现框架与应用程序之间的松耦合,是两者之间协作时常用的模式
    • 其中包括:模板方法,观察者模式(事件模式),策略模式

3.1 模板方法

  • 动机
    • 在软件构建过程中,对于某一项任务,常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固定的原因而无法和任务的整体结构同时实现
    • 如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求
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
// 程序库开发人员
class Library{
public:
void Step1(){
// ....
}
void Step3(){
// ....
}
void Step5(){
// ....
}
}
// 应用开发人员
class Application{
public:
void Step2(){
// ....
}
void Step4(){
// ....
}
}

// 程序的整体流程
int main(){
Library lib();
Application app();

lib.step1();

if(app.Step2()){
lib.Step3();
}

for(int i = 0; i<4; i++)
app.Step4();

lib.Step5();
}

// --------------
// 改进(使用模板方法)
// --------------
// 程序库开发人员
class Library{
public:
// 稳定模板方法
void Run(){
Step1();

if(Step2()){ //支持变化==>虚函数的多态调用
Step3();
}

for(int i = 0; i<4;++i)
Step4(); //支持变化==>虚函数的多态调用
Step5();
}
// Run方法中,稳定有变化,稳定的函数写成非虚函数,变化的函数写成虚函数

virtual ~Library(); //基类的析构函数为虚函数,在delete时可调用到子类的析构函数

protected:
void Step1(){
// ....
}
void Step3(){
// ....
}
void Step5(){
// ....
}

virtual bool Step2() =0; //变化
virtual void Step4() =0; //变化
}
// 应用程序开发人员
class Application : public Library{
protected:
virtual bool Step2(){
// ...子类重写实现
}
virtual void Step4(){
// ...子类重写实现
}
}
int main(){
Library* pLib = new Application();
pLib->Run();

delete pLib;
}
  • 程序库Library一般书写的早,而应用Application实现的晚
    • Application调用Libaray为晚绑定
      • 晚绑定机制包括函数指针和虚函数机制
    • Libaray调用Application为早绑定
  • 定义一个操作中的算法的骨架(稳定,Run函数),而将一些步骤延迟(变化)到子类中,模板方法使得子类可以不改变(复用)一个算法的结构即可重定义(override重写)该算法的某些特定步骤
  • 模板方法必须有一个稳定的骨架才可以适用该模式
    • 设计模式的假设条件必须有一个稳定点
    • 设计模式主要在稳定和变化中寻找稳定点,隔离变化和稳定

模板方法结构
图中TemplateMethod()为稳定的,而PrimitiveOperation1(),PrimitiveOperation2()为变化的

  • 要点总结
    • 模板方法模式是一种非常基础性的设计模式,面向系统中有大量的应用,用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构
    • 除了可以灵活应对子步骤的变化外,内含的反向控制结构式模板方法模式的典型应用
    • 在具体实现方面,被模板方法模式调用的虚方法可以具有实现,也可以没有任何实现,但一般推荐将其设置为protected方法

3.2 策略模式

  • 动机
    • 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一种性能负担
    • 如果在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
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
enum TaxBase{
CN_Tax,
US_Tax,
DE_Tax
};

class SalesOrder{
TaxBase tax;
public:
double CalculateTax(){
// ...

if(tax == CN_Tax){
// CN********
}
else if(tax == US_Tax){
// US********
}
else if(tax == DE_Tax){
// DE********
}
// ...
}
}

// 以上代码违反开放封闭原则

// -----------
// 使用策略模式
// -----------
class TaxStrategy{
public:
virtual double Calculate(const Contex& contex) =0;
virtual ~TaxStrategy(){}
};

class CNTax : public TaxStrategy{
public:
virtual double Calculate(const Contex& contex){
// ************
}
};

class USTax : public TaxStrategy{
public:
virtual double Calculate(const Contex& contex){
// ************
}
};

class DETax : public TaxStrategy{
public:
virtual double Calculate(const Contex& contex){
// ************
}
};

class SalesOrder{
private:
TaxStrategy* strategy;
public:
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory -> NewStrategy(); //使用工厂模式
}

~SalesOrder(){
delete this->strategy;
}

public double CalculateTax(){
// ...
Contex contex();
double val = strategy->Calculate(contex); //多态调用
}
}
  • 模式定义:定义一系列算法,将他们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于它的客户程序(稳定,SalesOrder)而变化(扩展,子类化)

策略模式
Context(SalesOrder)与Strategy(TaxStrategy)为稳定的,而ConcreteStrategyA,ConcreteStrategyB,ConcreteStrategyC为变化的部分(子类)

  • 要点总结
    • 策略模式及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换
    • 策略模式提供了用条件判断语句以外得另一种选择,消除条件判断语句就是在解耦合。含有许多条件判断语句的代码通常都需要策略模式
      • 在判断条件绝对不变时,可不使用策略模式
    • 如果策略对象没有实例变量,那么各个上下文可以共享同一个策略对象(单例模式),从而节省对象开销

3.3 观察者模式

  • 动机
    • 在软件构建过程中,需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生变化,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化
    • 使用面向对象技术,可以将依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// MainForm.cpp
class MainForm : public Form{
TextBox* txtFilePath;
TextBox* txtFileNumber;

// 增加processbar的需求
ProgressBar* progressBar;

public:
void Button1_Click(){
string filePath = txtFilePath -> getPath();
int number = atoi(txtFileNumber->getText().c_str());

FileSplitter splitter(filePath, number, processBar);

splitter.split();
}
};

// 更改,使用多继承
// 不推荐使用多继承,但推荐一个是主继承类,而其他的继承类为接口
class MainForm : public Form, public IProgress{
TextBox* txtFilePath;
TextBox* txtFileNumber;

// 增加processbar的需求
ProgressBar* progressBar;

public:
void Button1_Click(){
string filePath = txtFilePath -> getPath();
int number = atoi(txtFileNumber->getText().c_str());

FileSplitter splitter(filePath, number, this);
// 当前this实现了IProgress接口

splitter.split();
}
virtual void DoProgress(float value){
processBar -> setValue(value);
}
};

// 更改,支持多个通知
class MainForm : public Form, public IProgress{
TextBox* txtFilePath;
TextBox* txtFileNumber;

// 增加processbar的需求
ProgressBar* progressBar;

public:
void Button1_Click(){
string filePath = txtFilePath -> getPath();
int number = atoi(txtFileNumber->getText().c_str());

ConsoleNotifier cn;

FileSplitter splitter(filePath, number);

splitter.add_IProgress(this);
splitter.add_IProgress(&cn);

splitter.split();

splitter.remove_IProgress(this);
}
virtual void DoProgress(float value){
processBar -> setValue(value);
}
};

// 添加,支持多个通知
class ConsoleNotifier : public IProgress{
public:
virtual void DoProgress(float value){
cout << ".";
}
};

// FileSplitter.cpp
// 更改:增加
class IProgress{
public:
virtual void DoProgress(float value) = 0;
virtual ~IProgress(){}
};


class FileSplitter{
string m_filePath;
int m_fileNumber;

// 违反DIP依赖倒置原则
// ProcessBar编译时依赖其他文件(ProcessBar为实现细节)
// ProcessBar实现细节易发生变化
ProgressBar* m_progressBar; // 扮演通知角色(通知控件)

// 更改:使用更加抽象的方式表达而不是用控件表达
IProgress* m_iprogress; // 抽象通知机制

// 更改:支持多个观察者
List<IProgress*> m_iprogressList; //支持多个观察者
// 根据删除和添加的性能选择数据结构

public:
FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) :
m_filePath(filePath),
m_fileNumber(fileNumber)
m_progressBar(progressBar){
}

// 更改
FileSplitter(const string& filePath, int fileNumber, IProgress* iprogress) :
m_filePath(filePath),
m_fileNumber(fileNumber)
m_iprogress(iprogress){
}

// 更改,支持多个观察者
FileSplitter(const string& filePath, int fileNumber) :
m_filePath(filePath),
m_fileNumber(fileNumber){
}
void add_IProgress(IProgress* iprogress){
m_iprogressList.push_back(iprogress);
}
void remove_IProgress(IProgress* iprogress){
m_iprogressList.remove(iprogress);
}

void split(){
// 1.读取大文件

// 2.分批次向小文件中写入
for(int i = 0; i< m_fileNumber; i++){
// ...

if(m_progressBar != nullptr){
m_progressBar -> setValue((i+1)/m_fileNumber); // 更新进度条
}
}
}

// 更改
void split(){
// 1.读取大文件

// 2.分批次向小文件中写入
for(int i = 0; i< m_fileNumber; i++){
// ...

if(m_iprogress != nullptr){
float progressValue = m_fileNumber;
progressValue = (i+1)/progressValue;
onProgress(progressValue);
}
}
}

// 优化
protected:
virtual void onProgress(float value){ //声明为虚函数以供子类重写
m_iprogress -> DoProgress(progressValue); // 更新进度条
}

// 更改,支持多个观察者
virtual void onProgress(float value){ //声明为虚函数以供子类重写
List<IProgress*>::Iterator start = m_iprogressList.begin();
while(itor != m_iprogressList.end()){
(*itor)->DoProgress(value);
itor++;
}
}
};

// 该设计中若需要支持多个通知(需要支持多个观察者)
  • 模式定义:定义对象间的==一种一对多【【(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新

观察者模式
subject和observer为稳定不变的,而ConcreteSubject,COncreteObserver为变化的为具体的实现部分

  • 要点总结
    • 使用面向对象的抽象,观察者模式使得可以独立地改变目标与观察者,从而使两者之间的依赖关系达到松耦合
    • 目标发送通知时,无需指定观察者,通知会自动传播
    • 观察者自己决定是否订阅通知,目标对象对此一无所知
    • 观察者模式使基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分

4 单一职责

  • 单一职责模式
    • 在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任
    • 典型模式
      • 装饰模式
      • 桥模式

4.1 装饰模式

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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// 业务操作
class Stream{
public:
virtual char Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual void Write(char data) = 0;

virtual ~Stream(){}
};

// 主体类
class FileStream : public Stream{
public:
virtual char Read(int number){
// 读文件流
}
virtual void Seek(int position){
// 定位文件流
}
virtual void Write(char data){
// 写文件流
}
};

class NetworkStream : public Stream{
public:
virtual char Read(int number){
// 读网络流
}
virtual void Seek(int position){
// 定位网络流
}
virtual void Write(char data){
// 写网络流
}
};

class MemoryStream : public Stream{
public:
virtual char Read(int number){
// 读内存流
}
virtual void Seek(int position){
// 定位内存流
}
virtual void Write(char data){
// 写内存流
}
};

// 扩展操作
class CryptoFileStream : public FileStream{
public:
virtual char Read(int number){
// 额外的加密操作
FileStream::Read(number); //读文件流
// 额外的加密操作
}
virtual void Seek(int position){
// 额外的加密操作
FileStream::Seek(position); //定位文件流
// 额外的加密操作
}
virtual void Write(byte data){
// 额外的加密操作
FileStream::Write(data); //写文件流
// 额外的加密操作
}
};
class CryptoNetworkStream : public NetworkStream{
public:
virtual char Read(int number){
// 额外的加密操作
NetworkStream::Read(number); //读网络流
// 额外的加密操作
}
virtual void Seek(int position){
// 额外的加密操作
NetworkStream::Seek(position); //定位网络流
// 额外的加密操作
}
virtual void Write(byte data){
// 额外的加密操作
NetworkStream::Write(data); //写网络流
// 额外的加密操作
}
};
class CryptoMemoryStream : public MemoryStream{
public:
virtual char Read(int number){
// 额外的加密操作
MemoryStream::Read(number); //读内存流
// 额外的加密操作
}
virtual void Seek(int position){
// 额外的加密操作
MemoryStream::Seek(position); //定位内存流
// 额外的加密操作
}
virtual void Write(byte data){
// 额外的加密操作
MemoryStream::Write(data); //写内存流
// 额外的加密操作
}
};

class BufferFileStream : public FileStream{
// ...
};
class BufferNetworkStream : public FileStream{
// ...
};
class BufferMemoryStream : public FileStream{
// ...
};

class CryptoBufferFileStream : public FileStream{
public:
virtual char Read(int number){
// 额外的加密操作
// 额外的缓冲操作
FileStream::Read(number); //读文件流
// 额外的加密操作
}
virtual void Seek(int position){
// 额外的加密操作
// 额外的缓冲操作
FileStream::Seek(position); //定位文件流
// 额外的加密操作
}
virtual void Write(byte data){
// 额外的加密操作
// 额外的缓冲操作
FileStream::Write(data); //写文件流
// 额外的加密操作
}
};

void Process(){
// 编译时装配
CryptoFileStream *fs1 = new CryptoFileStream();
BufferFileStream *fs2 = new BufferFileStream();
CryptoBufferFileStream *fs3 = new CryptoBufferFileStream();
};

// 上述代码,通过继承,使得子类的规模越来越大
// ------------------
// 使用装饰模式进行重构
// ------------------
// 业务操作
class Stream{
public:
virtual char Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual void Write(char data) = 0;

virtual ~Stream(){}
};

// 主体类
class FileStream : public Stream{
public:
virtual char Read(int number){
// 读文件流
}
virtual void Seek(int position){
// 定位文件流
}
virtual void Write(char data){
// 写文件流
}
};

class NetworkStream : public Stream{
public:
virtual char Read(int number){
// 读网络流
}
virtual void Seek(int position){
// 定位网络流
}
virtual void Write(char data){
// 写网络流
}
};

class MemoryStream : public Stream{
public:
virtual char Read(int number){
// 读内存流
}
virtual void Seek(int position){
// 定位内存流
}
virtual void Write(char data){
// 写内存流
}
};

// 扩展操作
// 需要中间类,当两个类之间具有相同的字段,需要将相同的字段提升到基类中
DecoratorStream : public Stream{
protected:
Stream* stream;

DecoratorStream(Stream* stream) : stream(stream){}
}

// 使用多态,在编译时重用,而在运行时不同
class CryptoStream : public DecoratorStream{ //加上基类Stream,为了完善接口规范
Stream* stream; //=new FileStream();=new NetworkStream();=new MemoryStream();
public:
// 需要构造器
CryptoStream(Stream* stream): DecoratorStream(stream){}

virtual char Read(int number){
// 额外的加密操作
stream -> Read(number); //读文件流
// 额外的加密操作
}
virtual void Seek(int position){
// 额外的加密操作
stream::Seek(position); //定位文件流
// 额外的加密操作
}
virtual void Write(byte data){
// 额外的加密操作
stream::Write(data); //写文件流
// 额外的加密操作
}
};

class BufferStream : public DecoratorStream{
Stream* stream;
public:
BufferStream(Stream* stream): DecoratorStream(stream){}
// ...
};


void Process(){
FileStream* s1 = new FileStream();
CryptoStream* s2 = new CryptoStream(s1);

BufferStream* s3 = new BufferStream(s1);
BufferStream* s3 = new BufferStream(s2);
};

转换之前的类关系图

经装饰模式转换之后

转换之后的类关系图

  • 动机
    • 在某些情况下可能会过度地使用继承来扩展对象地功能,由于继承为类引入过多的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导师更多的子类的膨胀
    • 使用装饰模式可以使对象功能的扩展能够根据需要来动态地实现,同时避免了扩展功能的增多带来的子类膨胀问题,从而使得任何功能扩展变化所导致的影响为最低
  • 定义:动态(组合)地给一个对象增加一些额外地职责。就增加功能而言,装饰模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)

装饰模式
Component,Decorator为稳定的,ConcreteComponent、ConcreteDecoratorA和ConcreteDecoratorB为变化的

  • 要点总结
    • 通过采用组合而非继承的手法,装饰模式是实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的灵活性差的和多个子类衍生的问题
    • 装饰类在接口上表现为is-a COmponent的继承关系;即装饰类继承了Component类所具有的接口,但在实现上又表现为has-a Component的组合关系,即装饰类又使用了另外一个Component类
      • 在设计中,若一个类继承了父类,并且在类中有一个父类的成员变量则怀疑该类为装饰类,使用的是装饰模式
    • 装饰模式的目的并非解决多子类衍生问题,其要点在于解决主体类在多个方向上的扩展功能(装饰)

4.2 桥模式

  • 动机
    • 由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度乃至多个纬度的变化
    • 解决多维度变化,利用面向对象技术使类型可以轻松沿着两个乃至多个方向变化,而不引入额外的复杂度
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
class Messager{
public:
virtual void Login(string username, string password)=0;
virtual void SendMessage(string message)=0;
virtual void SendPicture(Image image)=0;

// 以下代码频繁实现
virtual void PlaySound()=0;
virtual void DrawShape()=0;
virtual void WriteText()=0;
virtual void Connect()=0;

virtual ~Messager(){}
};

// 平台实现
class PCMessagerBase : public Messager{
public:
virtual void PlaySound(){
// ************
}
virtual void DrawShape(){
// ************
}
virtual void WriteText(){
// ************
}
virtual void Connect(){
// ************
}
};

class MobileMessagerBase : public Messager{
public:
virtual void PlaySound(){
// ============
}
virtual void DrawShape(){
// ============
}
virtual void WriteText(){
// ============
}
virtual void Connect(){
// ============
}
}

// 业务对象
class PCMessagerLite : public PCMessagerBase{
public:
virtual void Login(string username, string password){
PCMessagerBase::Connect();
// ......
}
virtual void SendMessage(string message){
PCMessagerBase::WriteText();
// ......
}
virtual void SendPicture(Image image){
PCMessagerBase::DrawShape();
// ......
}
};
class PCMessagerPerfect : public PCMessagerBase{
public:
virtual void Login(string username, string password){
PCMessagerBase::PlaySound();
// *******
PCMessagerBase::Connect();
}
virtual void SendMessage(string message){
PCMessagerBase::PlaySound();
// *******
PCMessagerBase::WriteText();
}
virtual void SendPicture(Image image){
PCMessagerBase::PlaySound();
// *******
PCMessagerBase::DrawShape();
}
};
class MobileMessagerLite : public MobileMessagerBase{
public:
virtual void Login(string username, string password){
MobileMessagerBase::Connect();
// ......
}
virtual void SendMessage(string message){
MobileMessagerBase::WriteText();
// ......
}
virtual void SendPicture(Image image){
MobileMessagerBase::DrawShape();
// ......
}
};
class MobileMessagerPerfect : public MobileMessagerBase{
public:
virtual void Login(string username, string password){
MobileMessagerBase::PlaySound();
// *******
MobileMessagerBase::Connect();
}
virtual void SendMessage(string message){
MobileMessagerBase::PlaySound();
// *******
MobileMessagerBase::WriteText();
}
virtual void SendPicture(Image image){
MobileMessagerBase::PlaySound();
// *******
MobileMessagerBase::DrawShape();
}
};

void Process(){
// 编译时装配
Messager *m = new MobileMessagerPerfect();
}

// -----------
// 使用装饰模式
// -----------
class Messager{
public:
virtual void Login(string username, string password)=0;
virtual void SendMessage(string message)=0;
virtual void SendPicture(Image image)=0;

// 以下代码频繁实现
virtual void PlaySound()=0;
virtual void DrawShape()=0;
virtual void WriteText()=0;
virtual void Connect()=0;

virtual ~Messager(){}
};

// 平台实现
class PCMessagerBase : public Messager{
public:
virtual void PlaySound(){
// ************
}
virtual void DrawShape(){
// ************
}
virtual void WriteText(){
// ************
}
virtual void Connect(){
// ************
}
};

class MobileMessagerBase : public Messager{
public:
virtual void PlaySound(){
// ============
}
virtual void DrawShape(){
// ============
}
virtual void WriteText(){
// ============
}
virtual void Connect(){
// ============
}
}

// 业务对象
class MessagerLite{
Messager* messager; // new PCMessagerBase()不成立
// 出现问题,PCMessagerBase为抽象类,纯虚基类
public:
virtual void Login(string username, string password){
message->Connect();
// ......
}
virtual void SendMessage(string message){
messager->WriteText();
// ......
}
virtual void SendPicture(Image image){
messager->DrawShape();
// ......
}
};
class MessagerPerfect{
Messager* messager;
public:
virtual void Login(string username, string password){
messager -> PlaySound();
// *******
messager -> Connect();
}
virtual void SendMessage(string message){
messager -> PlaySound();
// *******
messager -> WriteText();
}
virtual void SendPicture(Image image){
messager -> PlaySound();
// *******
messager -> DrawShape();
}
};

void Process(){
// 编译时装配
Messager *m = new MobileMessagerPerfect();
}

// ------------------------------------------------------------
// 由上述纯虚基类的问题,发现将两部分塞到一个类中不合理,将两部分拆分
// 且上述函数两部分为两个方向的实现,一个为平台实现,另一个为业务实现
// ------------------------------------------------------------
// 修改
class Messager{
protected:
MessagerImp* messagerImp; //重构
Messager(MessagerImp* messagerImp) : messagerImp(messagerImp){}; // 构造器
public:
virtual void Login(string username, string password)=0;
virtual void SendMessage(string message)=0;
virtual void SendPicture(Image image)=0;

virtual ~Messager(){}
};
class MessagerImp{
public:
// 以下代码频繁实现
virtual void PlaySound()=0;
virtual void DrawShape()=0;
virtual void WriteText()=0;
virtual void Connect()=0;

virtual ~Messager(){}
};

// 平台实现
class PCMessagerImp : public MessagerImp{
public:
virtual void PlaySound(){
// ************
}
virtual void DrawShape(){
// ************
}
virtual void WriteText(){
// ************
}
virtual void Connect(){
// ************
}
};

class MobileMessagerImp : public MessagerImp{
public:
virtual void PlaySound(){
// ============
}
virtual void DrawShape(){
// ============
}
virtual void WriteText(){
// ============
}
virtual void Connect(){
// ============
}
}

// 业务对象
class MessagerLite : public Messager{

MessagerImp* messagerImp;
public:
MessagerLite(MessagerImp* messagerImp) : messagerImp(messagerImp){};

virtual void Login(string username, string password){
messagerImp->Connect();
// ......
}
virtual void SendMessage(string message){
messagerImp->WriteText();
// ......
}
virtual void SendPicture(Image image){
messagerImp->DrawShape();
// ......
}
};
class MessagerPerfect : public Messager{
MessagerImp* messagerImp;
public:
MessagerPerfect(MessagerImp* messagerImp) : messagerImp(messagerImp){};

virtual void Login(string username, string password){
messagerImp -> PlaySound();
// *******
messagerImp -> Connect();
}
virtual void SendMessage(string message){
messagerImp -> PlaySound();
// *******
messagerImp -> WriteText();
}
virtual void SendPicture(Image image){
messagerImp -> PlaySound();
// *******
messagerImp -> DrawShape();
}
};

void Process(){
// 编译时装配
MessagerImp* messagerImp = new PCMessagerImp();
Messager* m = new Messager(messagerImp);
}
  • 定义
    • 将抽象部分(业务功能)与实现部分(平台实现)分离,使它们可以独立地变化

桥模式
Abstraction与Implementor为稳定的,其中Abstraction中含有Imp指针指向Implementor,RefinedAbstraction、ConcreteImplementorA与ConcreteImplementorB为变化的

  • 要点总结
    • 桥模式使用对象间的组合关系解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度变化。所谓抽象和实现沿着各自维度的变化,即子类化它们
    • 桥模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。桥模式是比多继承方案更好的解决方法
    • 桥模式的应用一般在两个非常强的变化维度,有时一个类也有多于两个的变化维度也可以使用桥模式的扩展模式

5 对象创建

  • 对象创建模式
    • 通过对象创建模式绕开(new),来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作
    • 其中包括:工厂模式,抽象工厂,原型模式和构建器

5.1 工厂模式

  • 动机
    • 在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化
    • 应对变化:需要绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// MainForm1.cpp
class MainFormpublic Form{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* processBar;
public:
void Button1_Click(){
string filePath = txtFilePath -> getText();
int number = atoi(txtFileNumber -> getText().c_str());

ISplitter* splitter =
new BinarySplitter(filePath,number); // 依赖具体类
// 需要进行面向接口的设计(依赖倒置原则,依赖抽象而不是依赖细节)
// 不能用new或在栈上创建对象,但可以使用返回一个对象的方法
splitter -> split();
}
};

// ISplitterFactory.cpp
class SplitterFactory{
public:
ISplitter* CreateSplitter(){
return new BinarySplitter();
}
}

class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
}

// FileSplitter1.cpp
class BinarySplitter : public ISplitter{
// ....
};

class TxtSplitter : public ISplitter{
// ....
}
class PictureSplitter : public ISplitter{
// ....
}
class VideoSplitter : public ISplitter{
// ....
}

// ------------
// 更改
// ------------
// MainForm2.cpp
class MainFormpublic Form{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* processBar;
public:
void Button1_Click(){
string filePath = txtFilePath -> getText();
int number = atoi(txtFileNumber -> getText().c_str());

SplitterFactory factory;
ISplitter* splitter =
factory.CreateSplitter();
splitter -> split();
}
};

// ISplitterFactory.cpp
class SplitterFactory{
public:
ISplitter* CreateSplitter(){
return new BinarySplitter();
// SplitterFactory依赖BinarySplitter
// MainForm依赖SplitterFactory
// 仍然产生编译时依赖
// 启发:使用虚函数(延迟到运行)产生运行时依赖
}
}

// ----------------
// 使用虚函数进行修改
// ----------------
// ISplitterFactory.cpp
// 抽象类
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
}
// 工厂基类
class SplitterFactory{
public:
virtual ISplitter* CreateSplitter() =0;
virtual ~SplitterFactory(){}
}

// MainForm2.cpp(只依赖抽象类和工厂基类)
class MainFormpublic Form{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* processBar;

SplitterFactory* factory; // 工厂
public:
// 使用构造器决定创建哪个
MainForm(SplitterFactory* factory){
this -> factory = factory;
}

void Button1_Click(){
string filePath = txtFilePath -> getText();
int number = atoi(txtFileNumber -> getText().c_str());

ISplitter* splitter =
factory->CreateSplitter(); // 通过虚函数实现多态new
splitter -> split();
}
};

// FileSplitter2.cpp
// 具体类
class BinarySplitter : public ISplitter{
// ....
};

class TxtSplitter : public ISplitter{
// ....
}
class PictureSplitter : public ISplitter{
// ....
}
class VideoSplitter : public ISplitter{
// ....
}

// 具体工厂
BinarySplitterFactory : public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new BinarySplitter();
}
}
TxtSplitterFactory : public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new TxtSplitter();
}
}
PictureSplitterFactory : public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new PictureSplitter();
}
}
VideoSplitterFactory : public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new VideoSplitter();
}
}
  • 模式定义
    • 定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂模式使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类

工厂模式
Product和Creator是稳定的,而ConcreteProduct和ConcreteCreator为变化的

  • 要点总结
    • 工厂模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合(new)会导致软件的脆弱
    • 工厂模式通过面向对象的手法(多态),将所要创建的具体对象工作延迟到子类,从而实现一种==扩展(而非更改)==的策略,较好地解决了紧耦合关系
    • 工厂模式解决了“单个对象”的需求变化。缺点在于要求创建方法/参数相同

5.2 抽象工厂

  • 动机:在软件系统中,经常面临着一系列相互依赖的对象的创建工作;同时由于需求的变化,往往存在更多系列对象的创建工作
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// EmployeeDAO.cpp
class EmployeeDAO{
public:
vector<EmployeeDAO> GetEmployees(){
SqlConnection* connection = new SqlConnection();
connection -> ConnectionString = "...";

SqlCommand* command = new SqlCommand();
command -> CommandText = "...";

SqlDataReader* reader = command -> ExecuteReader();
while(reader->Read()){}
}
};

// ----------------------------
// 实现多数据库的支持,面向接口设计
// ----------------------------
// 数据库有关的基类
class IDBConnection{
// 虚基类
};
class IDBConnectionFactory {
public:
virtual IDBConnectionFactory* CreateDBConnection()=0;
virtual ~IDBConnectionFactory(){}
};

class IDBCommand{

};
class IDBCommandFactory {
public:
virtual IDBCommandFactory* CreateDBCommand()=0;
virtual ~IDBCommandFactory(){}
};

class IDataReader{

};
class IDataReaderFactory {
public:
virtual IDataReaderFactory* CreateDBReader()=0;
virtual ~IDataReaderFactory(){}
};
// 支持Sql Server
class SqlConnection : public IDBConnection{

};
class SqlConnectionFactory {
public:
virtual SqlConnectionFactory* CreateDBConnection()=0;
virtual ~SqlConnectionFactory(){}
};

class SqlCommand : public IDBCommand{

};
class SqlCommandFactory {
public:
virtual SqlCommandFactory* CreateDBCommand()=0;
virtual ~SqlCommandFactory(){}
};

class SqlDataReader : public IDataReader{

}
class SqlDataReaderFactory {
public:
virtual SqlDataReaderFactory* CreateDBReader()=0;
virtual ~SqlDataReaderFactory(){}
};
// 支持Oracle
class OracleConnection : public IDBConnection{

};
class OracleConnectionFactory {
public:
virtual OracleConnectionFactory* CreateDBConnection()=0;
virtual ~OracleConnectionFactory(){}
};

class OracleCommand : public IDBCommand{

};
class OracleCommandFactory {
public:
virtual OracleCommandFactory* CreateDBCommand()=0;
virtual ~OracleCommandFactory(){}
};

class OracleDataReader : public IDataReader{

}
class OracleDataReaderFactory {
public:
virtual OracleDataReaderFactory* CreateDBReader()=0;
virtual ~OracleDataReaderFactory(){}
};

class EmployeeDAO{
IDBConnectionFactory* dbConnectionFactory;
IDBCommandFactory* dbCommandFactory;
IDBReaderFactory* dbReaderFactory;
// 以上三个对象必须是同系列的,由以下三个具有关联性

public:
vector<EmployeeDAO> GetEmployees(){
IDBConnection* connection = dbConnectionFactory->CreateDBConnection();
connection -> ConnectionString = "...";

IDBCommand* command = dbCommandFactory -> CreateDBCommand();
command -> CommandText = "...";
command -> SetConnection(connection); // 关联性

IDBDataReader* reader = command -> ExecuteReader(); // 关联性
while(reader->Read()){}
}
};

// ------------------
// 使用抽象工厂进行改善
// ------------------
// 三个工厂类具有相关性,可以将三个工厂放在一个类中,改善该问题
class IDBConnection{
// 虚基类
};
class IDBCommand{

};
class IDataReader{

};
class IDBFactory {
public:
virtual IDBConnection* CreateDBConnection()=0;
virtual IDBCommand* CreateDBCommand()=0;
virtual IDataReader* CreateDBReader()=0;
virtual ~IDBFactory(){}
};

// 支持Sql Server
class SqlConnection : public IDBConnection{

};
class SqlCommand : public IDBCommand{

};
class SqlDataReader : public IDataReader{

}
class SqlDBFactory : public IDBFactory{
public:
virtual IDBConnection* CreateDBConnection()=0;
virtual IDBCommand* CreateDBCommand()=0;
virtual IDataReader* CreateDBReader()=0;
virtual ~SqlDBFactory(){}
};

// 支持Oracle
class OracleConnection : public IDBConnection{

};
class OracleCommand : public IDBCommand{

};
class OracleDataReader : public IDataReader{

}
class OracleDBFactory : public IDBFactory{
public:
virtual IDBConnection* CreateDBConnection()=0;
virtual IDBCommand* CreateDBCommand()=0;
virtual IDataReader* CreateDBReader()=0;
virtual ~OracleDBFactory(){}
};

class EmployeeDAO{
IDBFactory* dbFactory;

public:
vector<EmployeeDAO> GetEmployees(){
IDBConnection* connection = dbFactory -> CreateDBConnection();
connection -> ConnectionString = "...";

IDBCommand* command = dbFactory -> CreateDBCommand();
command -> CommandText = "...";
command -> SetConnection(connection); // 关联性

IDBDataReader* reader = command -> ExecuteReader(); // 关联性
while(reader->Read()){}
}
};
  • 模式定义
    • 提供一个接口,让该接口负责创建一系列“相关或相互依赖的对象”,无需指定它们具体的类

抽象工厂

  • 要点总结
    • 如果没有应对多系列对象构建的需求变化,则没有必要使用抽象工厂模式,这时候使用简单的工厂完全可以
    • 系列对象指的是某一特定条件下对象之间有相互依赖、或作用的关系。不同系列的对象之间不能相互依赖
    • 抽象工厂模式主要在于应对新系列的需求变化。其缺点在于难以应对新对象的需求变化

5.3 原型模式

  • 动机:在软件系统中,经常面临着某些结构复杂的对象的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口
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
// ISplitterFactory.cpp
// 抽象类
// 将之前的两个类合并
class ISplitter{
public:
virtual ISplitter* clone() =0; //通过克隆自己创建
virtual void split()=0;
virtual ~ISplitter(){}

}

// MainForm.cpp
class MainFormpublic Form{
ISplitter* prototype; //原型对象

public:
MainForm(ISplitter* prototype){
this -> prototype = prototype;
}

void Button1_Click(){

ISplitter* splitter =
prototype->clone(); //克隆原型
splitter -> split();
}
};

// FileSplitter.cpp
// 具体类
class BinarySplitter : public ISplitter{
// ....
public:
virtual ISplitter* clone(){
return new BinarySplitter(*this); // 拷贝构造函数
}
};

class TxtSplitter : public ISplitter{
// ....
public:
virtual ISplitter* CreateSplitter(){
return new TxtSplitter(*this);
}
}
class PictureSplitter : public ISplitter{
// ....
public:
virtual ISplitter* CreateSplitter(){
return new PictureSplitter(*this);
}
}
class VideoSplitter : public ISplitter{
public:
virtual ISplitter* CreateSplitter(){
return new VideoSplitter(*this);
}
}
  • 模式定义:使用原型实例指定创建对象的种类,然后通过==拷贝(深克隆)==这些原型来创建新的对象
    • 若创建对象使用工厂方法较为简单,则使用工厂方法,若创建时需要保留对象的某些特性,则使用原型模式利用拷贝创建对象
  • 要点总结(不常用)
    • 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,同时要求这些易变类拥有稳定的接口
    • 原型模式对于如何创建易变类的实体对象采用原型克隆的方法来做,使得可以非常灵活地动态创建拥有某些稳定接口的新对象——所需工作仅仅使注册一个新类的对象(即原型),然后在任何需要的地方Clone(C++中利用拷贝构造函数)
    • 原型模式中的Clone方法可以利用某些框架中的序列化来实现深拷贝

5.4 构建器

  • 动机
    • 在软件系统中,有时候面临着一个复杂对象的创建工作,其通常由各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定(注意与模板方法对比)
    • 提供一种封闭机制来隔离出复杂对象的各个部分的变化,从而保持系统中的稳定构建算法不随着需求改变而改变
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
129
130
131
132
133
134
class House{
public:
// C++中构造函数调用虚函数,为静态绑定,不是动态绑定,不会调用子类的虚函数版本
// 子类的构造函数先调用父类的构造函数
// 若父类的构造函数可以调用子类的虚函数,则子类构造函数还没有调用(子类没有生成)就开始调用其函数,违背对象的生成规则
void Init(){
this -> BuildPart1();
for(int i = 0; i< 4; i++)
this -> BuildPart2();

bool flag = this->BuildPart3();

if(flag){
this -> BuildPart4();
}

this -> BuildPart5();
}

virtual ~House(){}

protected:
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;

}

class StoneHouse : public House{
protected:
virtual void BuildPart1(){
// ...
}
virtual void BuildPart2(){
// ...
}
virtual void BuildPart3(){
// ...
}
virtual void BuildPart4(){
// ...
}
virtual void BuildPart5(){
// ...
}
}

// 使用时
int main(){
House* pHouse = new StoneHouse();
pHouse -> Init(); // 完成构建
}

// ------------------------------------
// 优化空间:对象过于复杂时,对对象进行拆分
// ------------------------------------
class House{
// .....
}

class HouseBuilder{
public:

House* GetResult(){
return pHouse;
}
virtual ~House(){}

protected:

House* pHouse;

virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
}

class StoneHouse : public House{
}

class StoneHouseBuilder : public HouseBuilder{
protected:
virtual void BuildPart1(){
// ...
}
virtual void BuildPart2(){
// ...
}
virtual void BuildPart3(){
// ...
}
virtual void BuildPart4(){
// ...
}
virtual void BuildPart5(){
// ...
}
}

class HouseDirector{
public:
HouseBuilder* pHouseBuilder;

HouseDirector(HouseBuilder* pHouseBuilder){
this -> pHouseBuilder = pHouseBuilder;
}

House* Construct(){
pHouseBuilder -> BuildPart1();
for(int i = 0; i< 4; i++)
pHouseBuilder -> BuildPart2();

bool flag = pHouseBuilder->BuildPart3();

if(flag){
pHouseBuilder -> BuildPart4();
}

pHouseBuilder -> BuildPart5();

return pHouseBuilder -> GetResult();
}
}

// 使用时
int main(){
HouseBuilder* pHouseBuilder = new StoneHouseBuilder();
HouseDirector* pHouseDirector = new HouseDirector(pHouseBuilder);
pHouseDirector -> Construct();
}
  • 模式定义:将一个复杂对象的构造与其表示相分离,使用相同的构建过程(稳定)可以创建不同的表示(变化)

构建器

Director和Builder为稳定的,而ConcreteBuilder为变化的

  • 要点总结
    • 构建器主要用于分步骤构建一个复杂的对象。在这其中分步骤是一个稳定的算法,而复杂对象的各个部分则经常变化
    • 变化点在哪里,封装哪里——构建器模式主要在于应对复杂对象各个部分的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动
    • 在构建器模式中,要注意不同语言中构造器内调用虚函数的差别

6 对象性能模式

  • 对象性能模式
    • 面向对象很好地解决了抽象的问题,但是必不可免地要付出一定的代价。对于通常情况,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理
    • 包含:单件模式,享元模式

6.1 单件模式

  • 动机
    • 在软件系统中,经常有一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率
    • 需要绕过常规的构造器,提供一种机制来保证一个类只有一个实例
      • 是类设计者的责任,不是使用者的责任
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
class Singleton{
private: // 设置为私有,外部不能构造该对象
Singleton();
Singleton(const Singleton& other);
public:
static Singleton* getInstance();
static Singleton* m_instance;
};

Singleton* Singleton::m_instance = nullptr;

// 线程非安全版本
// 单线程中OK,在多线程中
Singleton* Singleton::getInstance(){
if(m_instance == nullptr){
m_instance = new Singleton();
}
return m_instance;
}

// 线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance(){
Lock lock; // 对一下4行进行加锁,执行完之后释放锁
if(m_instance == nullptr){
m_instance = new Singleton();
}
return m_instance;
}

// 双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance(){
if(m_instance == nullptr){
Lock lock;
if(m_instance == nullptr){
// 为了防止在上锁之前两个线程抢占加锁,需要进行二次判断
m_instance = new Singleton();
// reorder不安全:该行拆分为3个步骤,首先分配内存,接着调用构造器,最后将内存指针给m_instance
// 真正在CPU层次,3个步骤很有可能被reorder,分配内存后直接将内存指针给m_instance,最后调用构造器
}
}
return m_instance;
}

// C++11版本之后的跨平台实现(volatile)
std::atomic<Singleton*> Singleton::m_instance; // 原子对象
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
  • 模式定义:保证一个类仅有一个实例,并提供一个该实例的全局访问点
  • 要点总结
    • 单件模式中的实例构造器可以设置为protected以允许子类派生
    • 单件模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个实例,与单件模式的初衷违背
    • 如何实现多线程环境下安全的单件模式?注意对双检查锁的正确实现

6.2 享元模式

  • 动机
    • 在软件系统采用纯粹对象方法的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价——主要指内存需求方面的代价
    • 享元模式避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式进行操作
  • 模式定义
    • 运用共享技术有效地支持大量细粒度的对象
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
// 特点:对象创建之后状态不可以更改
class Font{
private:
// unique object key
string key;

// object state
// ...

public:
Font(const string& key){
// ...
}
};

class FontFactory{
private:
map<string, Font*> fontPool;

public:
Font* GetFont(const string& key){
map<string, Font*>::Iterator item = fontPool.fing(key);

if(item != fontPool,end()){
return fontPool[key]; // 共享的方式
}else{
Font* font = new Font(key);
fontPool[key] = font;
return font;
}
}

void clear(){
// ...
}
};
  • 要点总结
    • 面向对象很好地解决了抽象性问题,但是作为一个运行在机器中的程序尸体,需要考虑对象的代价问题。享元模式主要解决了面向对象的代价问题,一般不触及面向对象的抽象性问题
    • 享元模式采用了对象共享的做法来降低系统中的对象个数。从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理
    • 对象的数量太大从而导致对象内存开销加大——需要仔细根据具体应用情况进行评估,不能凭空臆断
      • 对象数量足够大时可以使用该模式

7 接口隔离模式

  • 接口隔离模式
    • 在组件构建过程中,某些接口之间直接的依赖常常会带来很多问题、甚至根本无法实现。采用添加一层==间接(稳定)==接口,来隔离本来互相紧密关联的接口是一种常见的解决方案
    • 包括:门面模式、代理模式、适配器、中介器

7.1 门面模式

  • 动机
    • 方案中存在组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,过多的耦合面临很多变化的挑战
    • 简化外部客户程序和系统间的交互接口,将外部客户程序的演化和内部子系统的变化之间的依赖相互解耦
  • 模式定义
    • 为子系统中的一组接口提供一个一致(稳定)的界面,门面模式定义了一个高层接口,该接口使子系统更加容易使用(复用)
  • 要点总结
    • 从客户程序的角度来看,门面模式简化了整个组件系统的接口,对于组件内部与外部客户程序来说,达到了解耦的效果——内部子系统的任何变化不会影响到门面接口的变化
    • 门面模式更加注重从架构的层次来看整个系统,而不是单个类的层次。门面模式很多时候更是一种架构设计模式
    • 门面模式并非一个集装箱,可以任意地放进任何多个对象。门面模式中组件的内部应该是“相互耦合关系比较大的一系列组件”,而不是一个简单的功能集合

7.2 代理模式

  • 动机
    • 在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或某些操作系统需要安全控制,或需要进程外的访问等),直接访问会给使用者、或系统结构带来很多麻烦
    • 代理模式在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性
      • 增加一层间接层是软件开发中常见的解决方式
  • 模式定义
    • 为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问

代理模式

Subject为稳定不变的,RealSubject为变化的,使用Proxy的realSubject指针间接访问RealSubject

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
// client.cpp
class ISubject{
pubilc:
virtual void process();
};

class RealSubject : public ISubject{
public:
virtual void process(){
// ...
}
};

class ClientApp{
ISubject* subject;
public:
ClientApp(){
subject = new RealSubject(); // 通过各种方式生成,但该操作不合适(由于性能原因或各种原因)
}

void DoTask(){
// ...
subject -> process();
// ...
}
}

// -----------
// 使用代理模式
// -----------
class ISubject{
pubilc:
virtual void process();
};

// Proxy的设计
class SubjectProxy : public ISubject{
// RealSubject realSubject;
public:
virtual void process(){
// 对RealSubject的简介访问
// ...
}
};

class ClientApp{
ISubject* subject;
public:
ClientApp(){
subject = new SubjectProxy();
}

void DoTask(){
// ...
subject -> process();
// ...
}
}
  • 要点总结
    • 增加一层间接层是软件系统中对许多复杂问题的一种常见解决方法。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的proxy对象便是解决这一问题的常用手段
    • 具体代理模式的实现方法、实现粒度相差很大,有可能对单个对象做细粒度的控制,如copy-on-write技术,有些可能对组件模块提供抽象代理层,在架构层次对对象做proxy
    • proxy并不一定要求接口完整的一致性,只要能够间接控制,有时候损及一些透明性是可以接受的

7.3 适配器

  • 动机
    • 在软件系统中,由于应用环境的变化,常常需要将一些现存的对象放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的
    • 使用适配器应对迁移变化,既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口
  • 模式定义
    • 将一个类的接口转换成客户希望的另一个接口。适配器模式使得原来由于接口不兼容而不能一起工作的那些类可以一起工作

适配器

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
// 目标接口(新接口)
class ITarget{
public:
virtual void process()=0;
};
// 遗留接口(老接口)
class IAdaptee{
public:
virtual void foo(int data) = 0;
virtual int bar() = 0;
};

// 遗留类型
class OldClass : public IAdaptee{
// ...
};

// 对象适配器:通过组合对象来实现
class Adapter : public ITarget{ //继承
protected:
IAdaptee* pAdaptee; // 组合了一个对象
public:
Adapter(IAdaptee* pAdaptee){
this->pAdaptee = pAdaptee;
}
virtual void process(){
// 伪码表示
int data = pAdaptee -> bar();
pAdaptee -> foo(data);
}
}

// 类适配器
class Adapter : public ITarget,
protected IAdaptee{ // 多继承
// 继承纯虚接口,需要自己实现内部函数,没有意义
// 常见方法是继承实体类
}
class Adapter : public ITarget,
protected OldClass{ // 多继承
// 继承OldClass将父类定死了,没有灵活性
}

int main(){
IAdaptee* pAdaptee = new OldClass();

// 使用旧的遗留类与适配器可以作为新的类型使用
ITarget* pTarget = new Adapter(pAdaptee);
pTarget -> process();
}
  • 要点总结
    • 适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,在遗留代码复用、类库迁移等方面非常有用
    • GOF23定义了两种适配器模式的实现结构:对象适配器和类适配器,但类适配器采用多继承的实现方式,一般不推荐使用。对象适配器采用对象组合的方式,更符合松耦合精神
    • 适配器模式可以实现的非常灵活,不必拘泥于GOF23中定义的两种结构。例如,完全可以将适配器模式中的现存对象作为新的接口方法参数来达到适配的目的

7.4 中介器

  • 动机
    • 在软件构建过程中,经常会出现多个对象互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化
    • 这种情况下,使用中介者模式来管理对象间的关联关系,表面相互交互对象之间的紧耦合引用关系,从而更好地抵御变化
  • 模式定义
    • 用一个中介对象来封装==(封装变化)一系列对象的交互。中介者使各对象不需要显式的相互引用(编译时依赖->运行时依赖)==,从而使其耦合松散(管理变化),而且可以独立地改变它们之间的交互

中介者模式

  • 要点总结
    • 将多个对象间复杂的关联关系解耦,中介器模式将多个对象间的控制逻辑进行集中管理,变“多个对象互相关联”为“多个对象和一个中介者关联”,简化了系统的维护,抵御了可能的变化
    • 随着控制逻辑的复杂化,中介器具体对象的实现可能相当复杂。这时候可能对中介对象进行分解处理
    • 门面模式是解耦系统间(单向)的对象关联关系;中介器模式是解耦系统内各个对象之间(双向)的关联关系

8 状态变化模式

  • 状态变化模式
    • 在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维持高层模式的稳定?“状态变化”模式为这一问题提供了一种解决方案
    • 典型模式:状态模式、备忘录

8.1 状态模式

  • 动机
    • 在软件构建过程中,某些对象的状态如果改变,其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同
    • 状态模式在运行时根据对象的状态来透明地改变对象的行为,而不会为对象操作和状态转化之间引入紧耦合
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
enum NetworkState{
Network_Open,
Network_Close,
Network_Connect,
};

class NetworkProcessor{
NetworkState state;
public:
void Operation1(){
if(state == Network_Open){
// *********
state = Network_Close;
}else if(state == Network_Close){
// .........
state = Network_Connect;
}else if(state == Network_Connect){
// $$$$$$$$$
state = Network_Open;
}
}

public void Operation2(){
if(state == Network_Open){
// *********
state = Network_Connect;
}else if(state == Network_Close){
// .........
state = Network_Open;
}else if(state == Network_Connect){
// $$$$$$$$$
state = Network_Close;
}
}

public void Operation3(){
}
};

// ----------
// 使用状态模式
// ----------
class NetworkState{
public:
NetworkState* pNext;
virtual void Operation1()=0;
virtual void Operation2()=0;
virtual void Operation3()=0;

virtual ~NetworkState(){}
};

class OpenState : public NetworkState{
// 注意使用单体模式
static NetworkState* m_instance;
public:
static NetworkState* getInstance(){
if(m_instance == nullptr){
m_instance = new OpenState();
}
}

void Operation1(){
// ************
pNext = CloseState::getInstance();
}

void Operation2(){
// ............
pNext = ConnectState::getInstance();
}

void Operation3(){
// $$$$$$$$$$$$
pNext = OpenState::getInstance();
}
}

class CloseState : public NetworkState{
// ...
}
class ConnectState : public NetworkState{
// ...
}

// 当添加一个新的状态时,只需要关注该新添加的状态的代码
// 为扩展的方法

class NetworkProcessor{
NetworkState* pState;
public:
NetworkProcessor(NetworkState* pState){
this -> pState = pState;
}

void Operation1(){
// ...
pState -> Operation1();
pState = pState -> pNext;
// ...
}
void Operation2(){
// ...
pState -> Operation2();
pState = pState -> pNext;
// ...
}
void Operation3(){
// ...
pState -> Operation3();
pState = pState -> pNext;
// ...
}
}
  • 模式定义
    • 允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为

状态模式

  • 要点总结
    • 状态模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象状态切换时,切换相应的对象;但同时维持State的接口,实现了具体操作与状态转换之间的解耦
    • 为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换时原子性的——即要么彻底转换过来,要么不转换
    • 如果State对象没有实例变量,那么各个上下文可以共享同一个State对象,从而节省对象开销

8.2 备忘录

  • 动机
    • 在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现
    • 备忘录实现对象状态的良好保存与恢复,同时不会因此破坏对象本身的封装性
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
class Memento{
string state;
public:
Memento(const string & s) : state(s) {}
string getState() const {return state;}
void setState(const string & s) {state = s;}
};

class Originator{
string state;
// ...
public:
Originator(){}
Memento createMomento(){
Memento m(state);
return m;
}
void setMomento(const Memento & m){
state = m.getState();
}
};

int main(){
Originator orginator;

//捕获对象状态,存储到备忘录
Memento mem = orginator.createMomento();

//... 改变orginator状态

//从备忘录中恢复
orginator.setMomento(mem);
}
  • 模式定义
    • 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态

备忘录模式

  • 要点总结
    • 备忘录存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态
    • 备忘录模式的核心是信息隐藏,即Originator需要向外隐藏信息,保持其封装性。但同时有需要将状态保持到外界(Memento)
    • 可以采用效率较高、又容易正确实现的序列化方案来实现备忘录模式

9 数据结构模式

  • 数据结构模式
    • 常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用。需要特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问
    • 典型模式:组合器、迭代器、职责链

9.1 组合器

  • 动机
    • 在软件在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现代码(而非抽象接口)的变化将引起客户代码的频繁变化,带来代码的维护性、扩展性等弊端
    • 组合器将客户代码与复杂的对象容器结构解耦,让对象容器自己实现自身的复杂结构,从而使客户代码就像处理简单对象一样处理复杂的对象容器
  • 模式定义
    • 将对象组合成树形结构以表示部分-整体的层次结构。组合其使得用户对单个对象和组合对象的使用具有一致性(稳定)
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
#include <iostream>
#include <list>
#include <string>
#include <algorithm>

using namespace std;

class Component
{
public:
virtual void process() = 0;
virtual ~Component(){}
};

//树节点
class Composite : public Component{
string name;
list<Component*> elements;
public:
Composite(const string & s) : name(s) {}

void add(Component* element) {
elements.push_back(element);
}
void remove(Component* element){
elements.remove(element);
}

void process(){
//1. process current node
//2. process leaf nodes
for (auto &e : elements)
e->process(); //多态调用
}
};

//叶子节点
class Leaf : public Component{
string name;
public:
Leaf(string s) : name(s) {}
void process(){
//process current node
}
};


void Invoke(Component & c){
//...
c.process();
//...
}


int main()
{
Composite root("root");
Composite treeNode1("treeNode1");
Composite treeNode2("treeNode2");
Composite treeNode3("treeNode3");
Composite treeNode4("treeNode4");
Leaf leat1("left1");
Leaf leat2("left2");

root.add(&treeNode1);
treeNode1.add(&treeNode2);
treeNode2.add(&leaf1);

root.add(&treeNode3);
treeNode3.add(&treeNode4);
treeNode4.add(&leaf2);

process(root);
process(leaf2);
process(treeNode3);
}

组合器

  • 要点总结
    • 组合器模式采用树形结构来实现普遍存在的对象容器,从而将一对多的关系转换成一对一的关系,使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器
    • 将客户代码与复杂的对象容器结构解耦是组合器的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的内部实现结构——发生依赖,从而更能应对变化
    • 组合器模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可以使用缓存技术来改善效率

9.2 迭代器

  • 动机
    • 在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种透明遍历也为同一种算法在多种集合对象上进行操作提供了可能
    • 使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方式
  • 模式定义
    • 提供一种方法顺序访问一个聚合对象中的各种元素,而==又不暴露(稳定)==该对象内部的表示
    • GOF中以面向对象(多态机制)的方式实现迭代器
    • 在STL的大背景下,该方法已经过时(泛型编程中的迭代器),迭代器使用模板(也是一种多态)实现编译时多态,性能较高,且泛型编程可以使接口的扩展性更高,且成本较低
      • 虚函数为运行时多态(性能有损失)

迭代器

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
template<typename T>
class Iterator
{
public:
virtual void first() = 0;
virtual void next() = 0;
virtual bool isDone() const = 0;
virtual T& current() = 0;
};



template<typename T>
class MyCollection{
public:
Iterator<T> GetIterator(){
//...
}

};

template<typename T>
class CollectionIterator : public Iterator<T>{
MyCollection<T> mc;
public:
CollectionIterator(const MyCollection<T> & c): mc(c){ }

void first() override {
// ...
}
void next() override {
// ...
}
bool isDone() const override{
// ...
}
T& current() override{
// ...
}
};

void MyAlgorithm()
{
MyCollection<int> mc;

Iterator<int> iter= mc.GetIterator();

for (iter.first(); !iter.isDone(); iter.next()){
cout << iter.current() << endl;
}
}

// 虚函数的调用具有性能成本,需要根据虚函数表进行查找
  • 要点总结
    • 迭代抽象:访问一个聚合对象的内容而无需暴露它内部表示
    • 迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作
    • 迭代器的健壮性考虑:遍历的同时改变迭代器所在的集合结构会导致问题

9.3 职责链

  • 动机
    • 在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显式指定,将必不可少地带来请求发送者与接受者地紧耦合
  • 模式定义
    • 使多个对象都有机会处理请求,从而避免请求地发送者和接收者之间地耦合关系。将这些对象 连成一条链(链表),并沿着这条链传递请求,直到有一个对象处理它为止
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
#include <iostream>
#include <string>

using namespace std;

enum class RequestType
{
REQ_HANDLER1,
REQ_HANDLER2,
REQ_HANDLER3
};

class Reqest
{
string description;
RequestType reqType;
public:
Reqest(const string & desc, RequestType type) : description(desc), reqType(type) {}
RequestType getReqType() const { return reqType; }
const string& getDescription() const {
return description;
}
};

class ChainHandler{
ChainHandler *nextChain;
void sendReqestToNextHandler(const Reqest & req){
if (nextChain != nullptr)
nextChain->handle(req);
}

protected:
virtual bool canHandleRequest(const Reqest & req) = 0;
virtual void processRequest(const Reqest & req) = 0;

public:
ChainHandler() {
nextChain = nullptr;
}
void setNextChain(ChainHandler *next) {
nextChain = next;
}

void handle(const Reqest & req){
if (canHandleRequest(req))
processRequest(req);
else
sendReqestToNextHandler(req);
}
};


class Handler1 : public ChainHandler{
protected:
bool canHandleRequest(const Reqest & req) override{
return req.getReqType() == RequestType::REQ_HANDLER1;
}
void processRequest(const Reqest & req) override{
cout << "Handler1 is handle reqest: " << req.getDescription() << endl;
}
};

class Handler2 : public ChainHandler{
protected:
bool canHandleRequest(const Reqest & req) override{
return req.getReqType() == RequestType::REQ_HANDLER2;
}

void processRequest(const Reqest & req) override{
cout << "Handler2 is handle reqest: " << req.getDescription() << endl;
}
};

class Handler3 : public ChainHandler{
protected:
bool canHandleRequest(const Reqest & req) override{
return req.getReqType() == RequestType::REQ_HANDLER3;
}
void processRequest(const Reqest & req) override{
cout << "Handler3 is handle reqest: " << req.getDescription() << endl;
}
};

int main(){
Handler1 h1;
Handler2 h2;
Handler3 h3;
h1.setNextChain(&h2);
h2.setNextChain(&h3);

Reqest req("process task ... ", RequestType::REQ_HANDLER3);
h1.handle(req);
return 0;
}

职责链

  • 要点总结
    • 职责链模式地应用场景在于“一个请求可能有多个接收者,但是最后真正地接收者只有一个”,这时候请求发送者与接收者地耦合有可能出现“变化脆弱”地症状,职责链地目的就是将二者解耦,从而更好地应对变化
    • 应用了职责链模式后,对象的职责分派将更具灵活性,可以在运行时动态添加/修改请求地处理职责
    • 如果请求传递到职责链地末尾仍得不到处理,应该有一个合理地缺省机制,这是每一个接受对象地责任,而不是发出请求地对象的责任

10 行为变化模式

  • 行为变化模式
  • 在组件的构建过程中,组件行为的变化经常导致组件本身剧烈的变化。“行为变化”模式将组件的行为和组件本身进行解耦,从而支持组件行为的变化,实现两者之间的松耦合
  • 典型模式:命令模式,访问器模式

10.1 命令模式

  • 动机
    • 在软件构建过程中,行为请求者与行为实现者通常呈现一种紧耦合。但在某些场合——比如需要对行为进行记录,撤销/重,事务等处理,这种无法抵御变化的紧耦合是不合适的
  • 模式定义
    • 将请求(行为)封装为一个对象,从而使得可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作

命令模式

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
#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Command{
public:
virtual void execute() = 0;
};

class ConcreteCommand1 : public Command{
string arg;
public:
ConcreteCommand1(const string & a) : arg(a) {}
void execute() override{
cout<< "#1 process..."<<arg<<endl;
}
};

class ConcreteCommand2 : public Command{
string arg;
public:
ConcreteCommand2(const string & a) : arg(a) {}
void execute() override{
cout<< "#2 process..."<<arg<<endl;
}
};


class MacroCommand : public Command{
vector<Command*> commands;
public:
void addCommand(Command *c){
commands.push_back(c);
}
void execute() override{
for (auto &c : commands)
c->execute();
}
};

int main(){
ConcreteCommand1 command1(receiver, "Arg ###");
ConcreteCommand2 command2(receiver, "Arg $$$");

MacroCommand macro;
macro.addCommand(&command1);
macro.addCommand(&command2);

macro.execute();
}
  • 要点总结
    • 命令模式的根本目的在于将行为请求者与行为实现者解耦,在面向对象语言中,常见的实现手段是讲行为抽象为对象
    • 实现命令接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。通过使用Composite模式,可以将多个命令封装成一个复合命令MacroCommand
    • 命令模式与C++中函数对象有些类似。但两者定义行为接口的规范有所区别:
      • 命令模式以面向对象中的接口-实现来定义行为接口规范,更严格,但有性能损失
      • C++函数对象以函数签名来定义行为接口规范,更灵活,性能更高,并且使用模板,为编译时多态,运行效率更高

10.2 访问器模式

  • 动机
    • 在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做更改,将会给子类带来繁重的变更负担,甚至破坏原有设计
    • 访问器模式在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作
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
// 使用访问器模式之前
// 完成开发后,添加新需求,需要对基类进行修改,违反开放封闭原则
#include <iostream>
using namespace std;
class Visitor;

class Element{
public:
virtual void Func1()=0;
virtual void Func2(int data)=0; // 添加的新方法
virtual ~Element(){}
};

class ElementA : public Element{
public:
void Func1() override{
// ...
}
void Func2(int data) override{ // 变更
// ...
}
};

class ElementB : public Element{
public:
void Func1() override{
// ...
}
void Func2(int data) override{ //变更
// ...
}
};

// -----------
// 使用访问器模式
// -----------
#include <iostream>
using namespace std;

class Visitor;

class Element{
public:
virtual void accept(Visitor& visitor) = 0; //第一次多态辨析
virtual ~Element(){}
};

class ElementA : public Element{
public:
void accept(Visitor &visitor) override {
visitor.visitElementA(*this);
}
};

class ElementB : public Element{
public:
void accept(Visitor &visitor) override {
visitor.visitElementB(*this); //第二次多态辨析
}
};


class Visitor{
public:
virtual void visitElementA(ElementA& element) = 0;
virtual void visitElementB(ElementB& element) = 0;
virtual ~Visitor(){}
};

//==================================
// 添加新的需求

//扩展1
class Visitor1 : public Visitor{
public:
void visitElementA(ElementA& element) override{
cout << "Visitor1 is processing ElementA" << endl;
}

void visitElementB(ElementB& element) override{
cout << "Visitor1 is processing ElementB" << endl;
}
};

//扩展2
class Visitor2 : public Visitor{
public:
void visitElementA(ElementA& element) override{
cout << "Visitor2 is processing ElementA" << endl;
}

void visitElementB(ElementB& element) override{
cout << "Visitor2 is processing ElementB" << endl;
}
};

int main()
{
Visitor2 visitor;
ElementB elementB;
elementB.accept(visitor);// double dispatch(二次多态辨析)

ElementA elementA;
elementA.accept(visitor);

return 0;
}
  • 模式定义
    • 表示一个作用于某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)

访问器模式

Visitor,Element要求稳定,并且要求ConcreteElementA和ConcreteElementB稳定

  • 缺点:当新增一个ConcreteElement时,visitor也需要跟着变化,此时会违反开闭原则

  • 要点总结

    • 访问器模式通过所谓双重分发来实现在不更改(不添加新的操作-编译时)Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类添加新的操作(支持变化)
    • 所谓双重分发即访问器模式中间包括了两个多态分发(注意其中的多态机制):第一个为accept方法的多态辨析;第二个为visitElementX方法的多态辨析
    • 访问器模式的最大缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变。因此Visitor模式适用于Element类层次结构稳定,而其中的操作却经常面临频繁改动

11 领域规则模式

  • 领域规则模式
    • 在特定领域中,某些变化虽然频繁,但是可以抽象为某种规则。这时候,结合特定领域,将问题抽象为语法规则,从而给出在该领域下的一般性解决方案
    • 典型模式:解析器模式

11.1 解析器模式

  • 动机
    • 在软件构建过程中,如果某一特定领域的问题比较复杂,类似的结构不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化
    • 解析器模式将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的
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
#include <iostream>
#include <map>
#include <stack>
using namespace std;

class Expression {
public:
virtual int interpreter(map<char, int> var)=0;
virtual ~Expression(){}
};

//变量表达式
class VarExpression: public Expression {
char key;
public:
VarExpression(const char& key){
this->key = key;
}
int interpreter(map<char, int> var) override {
return var[key];
}
};

//符号表达式
class SymbolExpression : public Expression {
// 运算符左右两个参数
protected:
Expression* left;
Expression* right;
public:
SymbolExpression( Expression* left, Expression* right): left(left),right(right){}
};

//加法运算
class AddExpression : public SymbolExpression {
public:
AddExpression(Expression* left, Expression* right): SymbolExpression(left,right){}
int interpreter(map<char, int> var) override{
return left->interpreter(var) + right->interpreter(var);
}
};

//减法运算
class SubExpression : public SymbolExpression {
public:
SubExpression(Expression* left, Expression* right): SymbolExpression(left,right){}
int interpreter(map<char, int> var) override {
return left->interpreter(var) - right->interpreter(var);
}
};

Expression* analyse(string expStr) {
stack<Expression*> expStack;
Expression* left = nullptr;
Expression* right = nullptr;
for(int i=0; i<expStr.size(); i++){
switch(expStr[i]){
case '+':
// 加法运算
left = expStack.top();
right = new VarExpression(expStr[++i]);
expStack.push(new AddExpression(left, right));
break;
case '-':
// 减法运算
left = expStack.top();
right = new VarExpression(expStr[++i]);
expStack.push(new SubExpression(left, right));
break;
default:
// 变量表达式
expStack.push(new VarExpression(expStr[i]));
}
}
Expression* expression = expStack.top();
return expression;
}

void release(Expression* expression){
//释放表达式树的节点内存...
}

int main(int argc, const char * argv[]) {
string expStr = "a+b-c+d-e";
map<char, int> var;
var.insert(make_pair('a',5));
var.insert(make_pair('b',2));
var.insert(make_pair('c',1));
var.insert(make_pair('d',6));
var.insert(make_pair('e',10));

Expression* expression= analyse(expStr);

int result=expression->interpreter(var);

cout<<result<<endl;

release(expression);

return 0;
}

解析器模式

  • 要点总结
    • 解析器模式的应用场合时解析器模式应用中的难点,只有满足业务规则频繁变化,且类似的结构不断重复出现,并且容易抽象为语法规则的问题才使用解析器模式
    • 使用解析器模式来表示文法规则,从而可以使用面向对象技巧来方便地扩展文法
    • 解析器模式比较适合简单的文法表示,对于复杂的文法表示,解析器模式会产生比较大的类层次结构,需要求助于语法分析生成器这样的标准工具

12 总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ----------
// C++对象模型
// ----------
class A:B{
// ...
}
// 该方式为继承方式,B放在A的前面

class A{
B b;
// ...
}
// 该方式为组合方式,但是仍为B在A的前面
// 组合的B为对象,会导致紧耦合

class A{
B* pb;
// ...
}
// 该方式为组合方式,但是是A中含有B的指针
// 组合的为B的指针,指向多态对象,带来松耦合,多态性,带来便利
// C++中的许多模式都为该模式
  • 什么时候不用模式
    • 代码可读性很差
    • 需求理解很浅
    • 变化没有显现
    • 不是系统的关键依赖点
    • 项目没有复用价值
    • 项目想要发布
  • 经验之谈
    • 不要为模式而模式
    • 关注抽象类&接口
    • 理清变化点和稳定点
    • 审视依赖关系
    • 要有Framework和Application的区隔思维
    • 良好的设计是演化的结果

评论