设计模式

设计模式

组件协作模式

现代软件分工之后的第一个结果是”框架与应用程序的划分”,”组件协作”模式通过晚期绑定来实现框架与应用程序的松耦合,是二者之间协作时常用的模式

典型模式:

  • Template Method
  • Strategy
  • Observer / Event

模板方法:

Motivation

在软件的构建过程中,对于某一项任务,它常常有稳定的整体结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(如框架与应用之间的关系)而无法和任务的整体结构同时实现

如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 库开发者
struct Library {
void step1() {}
void step3() {}
void step5() {}
};

// 应用开发人员
struct Application {
bool 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();
}
return 0;
}

显然如上设计是结构化的1 3 5 由库开发人员编写,2 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
// 库开发者
struct Library {
void Run() {
step1();
if(step2()) {step3();}
for(int i=0 ; i<4 ; ++i) {
step4();
}
step5();
}
virtual ~Library() {}
protected:
void step1() {} // 稳定
void step3() {} // 稳定
void step5() {} // 稳定
virtual bool step2() = 0; // 变化
virtual void step4() = 0; // 变化
};

// 应用开发人员
struct Application : Library {
bool step2() override {}
void step4() override {}
};

int main() {
Library* pLib = new Application;
pLib->Run();
delete pLib;
return 0;
}

如今库开发人员负责 1 3 5 以及程序的主流程,应用开发人员负责 2 4,这就实现了调用关系的反转,原来是应用开发人员在程序主流程中调用库函数这是早绑定,如今成为了库函数调用应用开发的函数(通过虚函数)这是晚绑定,极大的减少了应用程序开发人员的负担

define

定义一个操作中算法 的骨架(稳定),而将一些步骤(变化)延迟到子类中。 Template Method 使得子类可以不改变(复用)一个算法的结构即可重定义该算法(Run方法,稳定的)中某些特定的步骤。

  • Template Method 模式用最简洁的机制(虚函数多态)为很多应用程序框架提供了灵活的拓展点,是代码复用方面的基本实现结构
  • 除了可以灵活应对子步骤的变化之外,”不要调用我,让我来调用你”的反向控制结构,是 Template Method 的典型应用
  • 在具体实现方面,被 Template Method 调用的方法一般设计为 protected 方法,应为这些方法单独使用没有意义,只有在流程中使用才有意义

策略模式

Motivation

在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂,而且有时候支持不使用的算法也是一个性能负担,如何在运行时根据需要透明的更改对象的算法?将算法与对象本身解耦,从而避免上述问题?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum TaxBase {
CN_Tax,
US_Tax,
DE_Tax,
};
class SalesOrder {
TaxBase tax;
public:
double CalculateTax() {
if(tax == CN_Tax) {/**/}
else if(tax == US_Tax) {/**/}
else if(tax == DE_Tax) {/**/}
}
};

如上代码,我们通过枚举各国的税率,在CalculateTax中使用开关语句来进行计算。当我们业务需求变化的时候,如新增计算法国的税率需求,首先我们要添加枚举,之后再增加CalculateTax中的开关语句,这违反了开闭原则。

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
class TaxStrategy {
public:
virtual double Calculate(const Contex& contex) = 0;
virtual ~TaxStrategy(){};
};

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

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

class SalesOrder {
private:
TaxStrategy& strategy;
public:
SalesOrder(TaxStrategy& tax_strategy) : strategy(tax_strategy){}
double CalculateTax(const Contex& contex) {
strategy.Calculate(contex);
}
};

当我们要支持法国的税法时,我们要实现派生类继承TaxStrategy,不需要改变SalesOrder,在调用时只需传入不同的参数

define

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

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

观察者模式

Motivation

在软件构造的过程中,我们需要为某些对象建立一种”通知依赖关系”,一个对象(目标对象)的状态发生改变,所有依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。使用面向面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。

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
class FileSplitter {
string m_filePath;
int m_fileNumber;
ProcessBar* m_processBar; // 新增进度条参数
public:
FileSplitter(const string& filePath , int fileNumber , ProcessBar* processBar) // 新增进度条参数
: m_filePath(filePath) , m_fileNumber(fileNumber) , m_processBar(processBar) { }
void split() {
// 读取大文件
for(int i=0 ; i<m_fileNumber ; ++i) {
m_processBar->setvalue((i+1)/m_fileNumber); // 新增 进度条逻辑
}
}
};

class MainForm : public Form {
TextBox* txtFilePath; // 所要分割的文件路径
TextBox* txtFileNumber; // 所要分割的文件数目
ProcessBar* processBar; // 新增进度条
public:
void Button1_Click() {
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath , number);
splitter.split();
}
};

在原本的功能上我们需要添加进度条的功能,如上代码是我们最先能想到的,但是这违反了依赖倒置原则, FileSplitter 属于高层模块,其直接依赖了实现细节 ProgressBar

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
class Iprogress {     // 观察者抽象接口
public:
virtual void DoProgress(float value) = 0;
virtual ~Iprogress() {}
};

class FileSplitter { // 主体
string m_filePath;
int m_fileNumber;
vector<Iprogress*> m_iprogress; // 抽象的通知机制
public:
FileSplitter(const string& filePath , int fileNumber , Iprogress* iprogress) // 新增进度条参数
: m_filePath(filePath) , m_fileNumber(fileNumber) , m_iprogress(iprogress) { }

void split() {
// 读取大文件
for(int i=0 ; i<m_fileNumber ; ++i) {
float progressValue = (float)(i + 1) / m_fileNumber;
onProgress(progressValue);
}
}

void register(Iprogress* iprogress) { m_iprogress.push_back(iprogress);}
void unregister(Iprogress* iprogress) { m_iprogress.erase(iprogress);}
protected:
void onProgress(Context context) {
for_each(m_iprogress.begin() , m_iprogress.end() ,
[&context](auto&& m_iprogress){m_iprogress->DoProgress(context);})
}
};

class MainForm : public Form , public Iprogress { //观察者
TextBox* txtFilePath; // 所要分割的文件路径
TextBox* txtFileNumber; // 所要分割的文件数目
ProcessBar* processBar; // 新增进度条
void Button1_Click() {
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath , number , this);
splitter.split();
}

void void DoProgress(float value) override {
precessBar->setValue(value);
}
};

define

定义对象之间的一种一对多(变化)的依赖关系,以便当一个对象(subject)的状态发生改变时,所有依赖它的对象都得到通知并自动更新

  • 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使两者之间的依赖关系达到松耦合
  • 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播
  • 观察者自己决定是否需要订阅通知,目标对象一无所知

单一职责模式

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

典型模式

  • Decorator

  • Bridge

装饰模式

Motivation

在某些情况下我们可能会过度的使用继承来拓展对象的功能,由于继承为类型引入静态特质,使得这种拓展方式缺乏灵活性,并随着子类的增多(拓展功能的增多),各种子类的组合(拓展功能的组合)会导致更多子类的膨胀。如何使对象功能的拓展能够根据需要来动态地实现?同时避免拓展功能的增多带来的子类膨胀问题?从而使得任何功能拓展变化导致的影响降到最低

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
class Stream {
public:
virtual char Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual void Wirte(char data) = 0;
virtual ~Stream() {};
};
class FileStream : public Stream {
public:
char Read(int number) { }
void Seek(int position) { }
void Wirte(char data) { }
};
class NetStream : public Stream {
public:
char Read(int number) { }
void Seek(int position) { }
void Wirte(char data) { }
};
// 拓展操作
class CryptoFileStream : public FileStream {
public:
char Read(int number) override {
// 加密操作
FileStream::Read(number);
}
void Seek(int position) override {
// 加密操作
FileStream::Seek(position);
// 加密操作
}
void Wirte(char data) override {
// 加密操作
FileStream::Wirte(data);
// 加密操作
}
};
class CryptoNetStream : public NetStream {}; // 网络流加密
class BufferFileStream : public FileStream {}; // 文件流缓冲
class BufferNetStream : public NetStream {}; // 网络流缓冲
// 文件流即加密又缓冲
class CryptoBufferFileStream : public FileStream {}// 业务操作
class Stream {
public:
virtual char Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual void Wirte(char data) = 0;
virtual ~Stream() {};
};
class FileStream : public Stream {
public:
char Read(int number) { }
void Seek(int position) { }
void Wirte(char data) { }
};
class NetStream : public Stream {
public:
char Read(int number) { }
void Seek(int position) { }
void Wirte(char data) { }
};
// 拓展操作
class CryptoFileStream : public FileStream {
public:
char Read(int number) override {
// 加密操作
FileStream::Read(number);
}
void Seek(int position) override {
// 加密操作
FileStream::Seek(position);
// 加密操作
}
void Wirte(char data) override {
// 加密操作
FileStream::Wirte(data);
// 加密操作
}
};
class CryptoNetStream : public NetStream {}; // 网络流加密
class BufferFileStream : public FileStream {}; // 文件流缓冲
class BufferNetStream : public NetStream {}; // 网络流缓冲
// 文件流即加密又缓冲
class CryptoBufferFileStream : public FileStream {}

此图是如上代码的继承关系,我们有太多的子类,而且存在过多的重复代码,如各个子类的加密操作,我们进行重构

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
class Stream {
public:
virtual char Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual void Wirte(char data) = 0;
virtual ~Stream() {};
};

class FileStream : public Stream {
public:
char Read(int number) { }
void Seek(int position) { }
void Wirte(char data) { }
};
class NetStream : public Stream {
public:
char Read(int number) { }
void Seek(int position) { }
void Wirte(char data) { }
};

class DecoratorStream : public Stream { // 装饰类
protected:
Stream* stream;
DecoratorStream(Stream* stm) : stream(stm) {}
};

class CryptoStream : public Stream { //任然为流保持接口规范
Stream* stream; // FileStream NetStream
public:
CryptoStream(Stream* stm) : stream(stm) {}
char Read(int number) {
// 加密操作
stream->Read(number);
}
void Seek(int position) {
// 加密操作
stream->Seek(position);
// 加密操作
}
void Wirte(char data) {
// 加密操作
stream->Wirte(data);
// 加密操作
}
};
class BufferStream : public Stream { // 文件流缓冲
Stream* stream;
public:
BufferStream(Stream* stm) : stream(stm) {}
char Read(int number) {
// 缓冲操作
stream->Read(number);
}
void Seek(int position) {
// 缓冲操作
stream->Seek(position);
}
void Wirte(char data) {
// 缓冲操作
stream->Wirte(data);
}

};
// 文件流即加密又缓冲
class CryptoBufferStream : public Stream {...};

void process() {
FileStream* s1 = new FileStream();
CryptoStream* s2 = new CryptoStream(s1); //加密文件流
BufferStream* s3 = new BufferStream(s1); //加密缓冲流
}

如上已经满足需求了,当所有子类中拥有相同的成员时,我们可以将他提到基类中,显然提到Stream中是不合理的,所以我们也可以引入了装饰类

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
class Stream {
public:
virtual char Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual void Wirte(char data) = 0;
virtual ~Stream() {};
};

class FileStream : public Stream {
public:
char Read(int number) { }
void Seek(int position) { }
void Wirte(char data) { }
};
class NetStream : public Stream {
public:
char Read(int number) { }
void Seek(int position) { }
void Wirte(char data) { }
};

class DecoratorStream : public Stream { // 装饰类
protected:
Stream* stream;
DecoratorStream(Stream* stm) : stream(stm) {}
};

class CryptoStream : public DecoratorStream{ //任然为流保持接口规范
public:
CryptoStream(Stream* stm) : DecoratorStream(stm) {}
char Read(int number) {
// 加密操作
stream->Read(number);
}
void Seek(int position) {
// 加密操作
stream->Seek(position);
// 加密操作
}
void Wirte(char data) {
// 加密操作
stream->Wirte(data);
// 加密操作
}
};
class BufferStream : public DecoratorStream{ // 文件流缓冲
public:
BufferStream(Stream* stm) : DecoratorStream(stm) {}
char Read(int number) {
// 缓冲操作
stream->Read(number);
}
void Seek(int position) {
// 缓冲操作
stream->Seek(position);
}
void Wirte(char data) {
// 缓冲操作
stream->Wirte(data);
}

};
// 文件流即加密又缓冲
class CryptoBufferStream : public DecoratorStream{};

void process() {
FileStream* s1 = new FileStream();
CryptoStream* s2 = new CryptoStream(s1); //加密文件流
BufferStream* s3 = new BufferStream(s1); //加密缓冲流
}

我们引入了DecoratorStream类,用于拓展操作,拓展的类依赖Stream的派生类

define

动态(组合)地给一个对象增加一些额外的指责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)

  • 通过采用组合而非继承的首发,Decorator模式实现来运行时动态拓展对象功能的能力,而且可以根据需要拓展多个功能。避免来使用继承带来的灵活性差和多子类衍生问题

  • Decorator类在接口上表现为is-a Component继承关系,即Decorator类继承了Component类所拥有的接口。但实现上又变现为has-a Component的组合关系,即Decorator类又使用来另外一个Component

  • Decorator模式的目的并非解决多子类衍生的多继承问题,Decorator模式应用的要点在于解决主体类在多个方向上的拓展功能 – “装饰”的含义

桥模式

Motivation

由于某些类型的固有实现逻辑,使得它们具有两个变化的维度,乃至多个维度变化。如何应对这种”多维度的变化”? 如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度?

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
class Messageer {
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 ~Messageer() {}
};

//平台实现
class PCMessagerBase : public Messageer {
public:
virtual void PlaySound() override {}
virtual void DrawShape() override {}
virtual void WriteText() override {}
};

class MobileMessagerBase : public Messageer {
public:
virtual void PlaySound() override {}
virtual void DrawShape() override {}
virtual void WriteText() override {}
};

//业务抽象
class PCMessagerLite : public PCMessagerBase { // pc平台精简版
public:
virtual void Login(string username , string password) override {
PCMessagerBase::Connect();
//...
}
virtual void SendMessage(string message) override {
PCMessagerBase::WriteText();
//...
}
virtual void SendPicture(Image image) override {
PCMessagerBase::DrawShape();
//...
}
};

class PCMessagerPerfect : public PCMessagerBase { // pc平台完美版
public:
virtual void Login(string username , string password) override {
PCMessagerBase::PlaySound();
//...
PCMessagerBase::Connect();
}
virtual void SendMessage(string message) override {
PCMessagerBase::PlaySound();
PCMessagerBase::WriteText();
//...
}
virtual void SendPicture(Image image) override {
PCMessagerBase::PlaySound();
PCMessagerBase::DrawShape();
//...
}
};

class MobileMessagerPerfect : public MobileMessagerBase { // mobile平台精简版
public:
virtual void Login(string username , string password) override {
MobileMessagerBase::PlaySound();
//...
MobileMessagerBase::Connect();
}
virtual void SendMessage(string message) override {
MobileMessagerBase::PlaySound();
MobileMessagerBase::WriteText();
//...
}
virtual void SendPicture(Image image) override {
MobileMessagerBase::PlaySound();
MobileMessagerBase::DrawShape();
//...
}

};

class MobileMessagerLite : public PCMessagerBase { // mobile平台精简版
public:
virtual void Login(string username , string password) override {
MobileMessagerBase::Connect();
//...
}
virtual void SendMessage(string message) override {
MobileMessagerBase::WriteText();
//...
}
virtual void SendPicture(Image image) override {
MobileMessagerBase::DrawShape();
//...
}
};

假设我们的业务抽象为m平台实现为n,那我们类的数目就有 m * n 个,我们发现PcMessagerLite和MobileMessagerLite中存在大量的重复代码对应的perfect中也同样存在。

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
class Messager {
protected:
MessagerImpl* messageImpl; //运行时确定是pcMessagerBase or mobileMessagerBase
public:
Messager(MessagerImpl* impl) : messageImpl(impl) {}
virtual void Login(string username , string password) = 0;
virtual void SendMessage(string message) = 0;
virtual void SendPicture(Image image) = 0;
virtual ~Messager() = default;
};

class MessagerImpl {
virtual void PlaySound() = 0;
virtual void DrawShape() = 0;
virtual void WriteText() = 0;
virtual ~MessagerImpl() = default;
};

//平台实现
class PCMessagerBase : public MessagerImpl {
public:
virtual void PlaySound() override {}
virtual void DrawShape() override {}
virtual void WriteText() override {}
};

class MobileMessagerBase : public MessagerImpl {
public:
virtual void PlaySound() override {}
virtual void DrawShape() override {}
virtual void WriteText() override {}
};

//业务抽象
class MessagerLite : Messager { // 精简版
public:
MessagerLite(MessagerImpl* impl) : Messager(impl) {}
virtual void Login(string username , string password) override {
messageImpl->Connect();
//...
}
virtual void SendMessage(string message) override {
message->WriteText();
//...
}
virtual void SendPicture(Image image) override {
messageImpl->DrawShape();
//...
}
};

class MessagerPerfect : Messager { // 完美版
public:
MessagerPerfect(MessagerImpl* impl) : Messager(impl) {}
virtual void Login(string username , string password) override {
message->PlaySound();
//...
message->Connect();
}
virtual void SendMessage(string message) override {
message->PlaySound();
message->WriteText();
//...
}
virtual void SendPicture(Image image) override {
message->PlaySound();
message->DrawShape();
//...
}
};

void process() {
MessagerImpl* impl = new PCMessagerBase;
Messager* message = new MessagerLite(impl);
}

define

将抽象部分(业务功能)与实现部分(平台实现)分离,使他们可以独立的变化

  • Bridge 模式使用对象间的组合关系,解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各个维度的变化即子类化他们

  • Bridge模式有时类似于多继承方案,但是多继承方案往往违背单一职责模式(一个类只有一个变化的原因),复用型比较差。Bridge模式是比多继承方案更好的解决方法

  • Bridge模式的应用一般在两个非常强的变化维度,有时一个类也有多于两个的变化维度,这时可以使用Bridge的拓展模式

对象创建模式

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

典型模式

  • Factor Method

  • Abstract Factor

  • Prototype

  • Builder

工厂方法

Motivation

在软件系统中,经常面临着创建对象的工作,由于需求的变化,需要创建的对象的具体类型经常变化。如何应对这种变化?如何绕开常规的对象创建方法(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
class Form {};

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

class FileSplitter : ISplitter {
public:
void split() override {
// ...
}
};
class BinarySplitter : ISplitter {
public:
void split() override {
// ...
}
};
class TxtSplitter : ISplitter {
public:
void split() override {
// ...
}
};
class VideoSplitter : ISplitter {
public:
void split() override {
// ...
}
};

class MainForm : public Form {
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* processBar;
public:
void Button1_Click() {
string filePath = txtFilePath->toString();
int number = atoi(txtFileNumber->toString());
ISplitter* splitter = new FileSplitter(filePath , number); // 依赖实现细节(具体类)
splitter->split();
}
};

上述代码中我们抽象了接口ISplitter,但是我们在MainForm中任然使用了实现细节FileSplitter,这违反了依赖倒置原则

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
class ISplitter {
public:
virtual void split() = 0;
virtual ~ISplitter() {}
};

class SplitterFactory {
public:
// 接口:工厂创建子类的方法,Factory Method 名字的由来
virtual ISplitter* CreateSplitter() = 0;
virtual ~SplitterFactory() {}
};

class FileSplitter : public ISplitter {
public:
void split() override {
// ...
}
};
class BinarySplitter : public ISplitter {
public:
void split() override {
// ...
}
};
class TxtSplitter : public ISplitter {
public:
void split() override {
// ...
}
};

class FileSplitterFactory : SplitterFactory{
public:
ISplitter* CreateSplitter() override {
return new FileSplitter;
}
};

class BinarySplitterFactory : SplitterFactory{
public:
ISplitter* CreateSplitter() override {
return new BinarySplitter;
}
};

class TxtSplitterFactory : SplitterFactory{
public:
ISplitter* CreateSplitter() override {
return new BinarySplitter;
}
};

class MainForm : public Form {
SplitterFactory* factory;
public:
MainForm(SplitterFactory* factory) { // 依赖注入
this->factory =factory;
}
void Button1_Click() {
ISplitter* splitter = factory->CreateSplitter(); // 称为多肽new
splitter->split();
}
};

经过修改后,MainForm只依赖抽象基类而不依赖具体实现,我们将变化的依赖赶到了MainForm的构造函数中

define

定义一个用于创建对象的接口,让子类决定实例化那一个类。Factory Method 使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类

  • Factory Method 模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱

  • Factory Method 模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种拓展(而非更改)的策略,叫好的解决了这种紧耦合关系

  • Factory Method模式解决单个对象的需求变化。缺点在于要求创建方法参数相同

抽象工厂

Motivation

在软件系统中,经常面临一系列相互依赖的对象的创建工作,同时,由于需求的变化,往往存在很多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一中封装机制来避免客户程序和这种多系列具体对象创建工作的紧耦合?

1
2
3
4
5
6
7
8
9
10
11
12
class EmployeeDAO {
public:
vector<EmployeeD0> GetEmployees() {
SqlConnection* connection = new SqlConnection();
connection->ConnectionSring = "...";
SqlCommand* command = new SqlCommand();
command->CommandText = "...";
SqlDataReader* reader = command->ExecuteReader();
while(reader->Read()) {
}
}
};

如上代码数据库已经和sqlServer绑定死了,不适用于多种数据库的变化

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
// 数据库访问有关的基类
class IDBConnection {};
class IDBCommand {};
class IDBDataReader {};

// 支持SqlServer
class SqlConnection : public IDBConnection {

};
class SqlCommand : public IDBCommand {

};
class SqlDataReader : public IDataReader {

};

// 支持Oracle
class OracleConnection : public IDBConnection {

};
class OracleCommand : public IDBCommand {

};
class OracleDataReader : public IDataReader {

};

class EmployeeDAO {
public:
vector<EmployeeD0> GetEmployees() {
IDBConnection* connection = new SqlConnection();
connection->ConnectionSring = "...";
IDBCommand* command = new SqlCommand();
command->CommandText = "...";
command->SetConnection(connection);
IDBDataReader* reader = command->ExecuteReader();
while(reader->Read()) {

}
}
};

为了支持多种数据库我们定义来接口,用多态的方式完成,由于需要new 子类所以任然不满足依赖倒置。我们很容易想到用工厂方法进行改良

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class IDBConnection {};
class IDBCommand {};
class IDBDataReader {};

class IDBConnectionFactory { /*virtual create method*/};
class IDBCommandFactory {};
class IDBDataReaderFactory {};


// 支持SqlServer
class SqlConnection : public IDBConnection {

};
class SqlConnectionFactory : IDBConnectionFactory {};
class SqlCommand : public IDBCommand {

};
class SqlCommandFactory : IDBCommandFactory {};
class SqlDataReader : public IDataReader {

};

我们有三个接口,所以我们会有三个工厂接口IDBConnectionFactory IDBCommandFactory IDBDataReaderFactory,要实现SqlConnectionFactory用于创建SqlConnection,其他类如法炮制,在EmployeeDAO类中,要存放三个工厂接口的指针。由于Connection Command DataReader 之间存在关联性,必须操作同一类数据库。为了避免用户传参导致三者混乱我们有如下优化方法

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
class IDBConnection {};
class IDBCommand {};
class IDBDataReader {};

class IDBFactory { // 将三个工厂合为一个工厂
public:
virtual IDBConnection* CreateDBConnection() = 0;
virtual IDBCommand* CreateDBCommand() = 0;
virtual IDBDataReader* CreateDBDataReader() = 0;
};


// 支持SqlServer
class SqlConnection : public IDBConnection {

};
class SqlCommand : public IDBCommand {

};
class SqlDataReader : public IDataReader {

};

class SqlFactory : public IDBFactory {
virtual IDBConnection *CreateDBConnection() { return new SqlConnection; }
virtual IDBCommand *CreateDBCommand() { return new SqlCommand; }
virtual IDBDataReader *CreateDBDataReader() { return new SqlDataReader; }
};

// 支持Oracle
class OracleConnection : public IDBConnection {

};
class OracleCommand : public IDBCommand {

};
class OracleDataReader : public IDataReader {

};

class EmployeeDAO {
IDBFactory* DBfactory; // 构造函数,依赖注入
public:
vector<EmployeeD0> GetEmployees() {
IDBConnection* connection = DBfactory->CreateDBConnection();
connection->ConnectionSring = "...";
IDBCommand* command = DBfactory->CreateDBCommand();
command->CommandText = "...";
command->SetConnection(connection);
IDBDataReader* reader = DBfactory->CreateDBDataReader();
while(reader->Read()) {

}
}
};

由于connection command datareader之间存在关联性,我们把原来的三个工厂合为了一个工厂

define

提供一个接口(工厂),让该接口负责创建一系列相关或者相互依赖的对象(多个工厂函数),无需指定他们具体的类

  • 如果没有应对多系列对象构建的需求变化,则没有必要使用Abstract Factory模式,这时候简单工厂完全可以

  • 系列对象指的是在某一特定系列下的对象之间有相互依赖或做用关系。不同系列的对象之间不能相互依赖

  • Abstract Factory模式主要在于应对新系列的需求变动。其缺点在难以应对新对象的需求变动

原型模式

Motivation

在软件系统中,经常面临着某些结构复杂的对象的创建工作,由于需求的变化,这些对象经常面临剧烈的变化,但是他们却拥有比较稳定一致的接口。如何对应这种变化?如何向客户程序(使用这些对象的程序)隔离出这些易变对象,从而使得这些易变对象的客户程序不随着需求改变而变化?

我们对FactoryMethod的代码进行修改

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
class Form {};

class ISplitter {
public:
virtual void split() = 0;
virtual ISplitter* clone() = 0; // 通过clone自己来创建对象
virtual ~ISplitter() = default;
};

class FileSplitter : public ISplitter {
public:
void split() override {
// ...
}
ISplitter* clone() override {
return new FileSplitter(*this);
}
};
class BinarySplitter : public ISplitter {
public:
void split() override {
// ...
}
ISplitter* clone() override {
return new BinarySplitter(*this);
}
};
class TxtSplitter : public ISplitter {
public:
void split() override {
// ...
}
ISplitter* clone() override {
return new TxtSplitter(*this);
}
};

class MainForm : public Form {
ISplitter* prototype; // 用于clone,而不是直接使用
public:
MainForm(ISplitter* prototype) { // 依赖注入
this->prototype=prototype;
}
void Button1_Click() {
ISplitter* splitter = prototype->clone(); // 称为多肽new
splitter->split();
}
};

原型模式将抽象类和工厂基类进行了合并

define

使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象

  • Prototype模式同样用于隔离对象的使用者和具体类型(易变)之间的耦合关系,它同样要求这些易变类拥有稳定的接口

  • Prototype模式对于如何创建易变类的实体对象采用原型克隆方法来做,它使得我们可以非常灵活的动态的创建拥有某些稳定接口的新对象(所需工作仅仅是注册一个新类的对象即原型然后在任何需要的地方使用clone)

  • Prototype模式中的clone方法可以利用某些框架中的序列化来实现深拷贝

对象性能模式

面向对象很好的解决了抽象的问题,但是必不可免的要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理

典型模式

  • Singleton

  • Flywight

单件模式

Motivation

在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确以及良好的效率。如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?这应该是类设计者的职责,而不是使用者的责任

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
#include <atomic>
#include <cstddef>
#include <mutex>
class Singleton {
Singleton();
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;
//非线程安全
Singleton* Singleton::getInstance() {
if(m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//线程安全,但锁消耗过大,当对象已经创建
//再次调用getInstance属于读m_instance加锁无意义
Singleton* Singleton::getInstance() {
Lock lock;
if(m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
if(m_instance == nullptr) { // 1
Lock lock;
if(m_instance == nullptr) {
m_instance = new Singleton(); // 2
/*
1. 分配内存
2. 调用构造器
3. 将地址返回
在reorder后指令顺序可能变为
1. 分配内存
2. 将地址返回
3. 调用构造器
在经历指令重排后,线程一执行了1 2 步骤直接将地址返回
线程二进入getInstance函数,发现m_instance不为空直接返回
m_instance,但是此时m_instance所指地址并没有调用构造器
*/
}
}
return m_instance;
}

//c++11版本之后的跨平台实现
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);
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);
}
}
return tmp;
}

define

保证一个类仅有一个实例,并提供一个该实例的全局访问点

  • Singleton 模式中的实例构造器可以设为为protected以允许子类派生

  • Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例,与Singleton模式的初衷违背

享元模式

Motivation

在软件系统中采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价(主要指内存需求方面的代价)。如何避免大量细粒度对象问题的同时,让外部客户程序任然能够透明的使用面向对象的方式来进行操作?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Font {
string key;
public:
Font(const string& key) {

}
};

class FontFactory {
map<string , Font*> fontPool;
public:
Font* GetFont(const string& key) {
map<string , Font*>::iterator item = fontPool.find(key);
if(item != fontPool.end()) {
return footPool[key];
} else {
Font* font = new Font(key);
fontPool[key] = font;
return font;
}
return nullptr;
}
};

define

运用共享技术有效的支持大量细粒度的对象

  • 面向对象很好的解决了抽象性问题,但是作为一个运行在机器中程序实体,我们需要考虑对象的代价问题。Flyweight主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题

  • Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理

  • 对象的数量太大从而导致对象埃格努内存开销加大,什么样的数量才算大?这需要我们仔细根据具体应用情况进行评估,而不能凭空臆断

接口隔离模式

在组件构架过程中,某些接口之间直接的依赖常常会带来很多问题,甚至根本无法实现。采用添加一层间接(稳定)接口,来隔离本来互相紧密关联的接口是一种常见的解决方案

典型模式

  • Facade

  • Proxy

  • Adapter

  • Mediator

门面模式

Motivation

上述A方案的问题在于组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。如何简化外部客户程序和系统间交互的接口?如何将外部客户程序的演化和内部子系统的变化之间的依赖相互解耦?

define

为子系统中的一组接口提供一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

  • 从客户程序的角度看,Facade模式简化来整个组件系统的接口,对于组件内部与外部客户程序来说,达到了一种解耦的效果,内部子系统的任何变化不用影响到Facade接口的变化

  • Facade设计模式更注重从架构的层次去看整个系统,而不是单个类的层次。Facade很多时候更是一种架构设计模式

  • Facade设计模式并非一个集装箱,可以任意的放进任何多个对象。Facade模式中组件的内部应该是相互耦合关系比较大的一系列组件,而不是一个简单的功能集合

代理模式

Motivation

在面向对象系统中,有些对象由于某种原因(比如对象创建的开销大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者,或者系统结构带来很多麻烦。如何在不时去透明操作对象的同时来管理/控制这些对象特有的复杂性?增加一层间接层是软件开发中常见的解决方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ISubject {
public:
virtual void process() = 0;
};

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

class ClientApp {
ISubject* subject;
public:
ClientApp() {
subject = new RealSubject;
}
void DoTask() {
//...
subject->process();
//...
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ISubject {
public:
virtual void process() = 0;
};

class SubjectProxy : public ISubject {
virtual void process() {
// 对RealSubject的间接访问
}

};

class ClientApp {
ISubject* subject;
public:
ClientApp() {
subject = new SubjectProxy;
}
void DoTask() {
//...
subject->process();
//...
}
};

define

为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。

  • 增加一层间接层是软件系统中对许多复杂问题的一种常见解决方法。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的proxy对象便是这一问题的常用手段

  • 具体proxy设计模式的实现方法,实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如copy-on-write技术,有些可能对组件模块提供抽象代理层,在架构层次对对象做proxy

  • Proxy并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时候损失一些透明性是可以接受的

适配器模式

Motivation

在软件系统中,由于应用环境的变化,常常需要将一些现存的对象放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。如何应对这种迁移的变化?如何即能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ITarget {     // 目标接口(新接口)
public:
virtual void process() = 0;
};

class IAdaptee { // 遗留接口(老接口)
public:
virtual void foo(int data) = 0;
virtual int bar() = 0;
};

class Adapter : public ITarget { // 适配器
protected:
IAdaptee* pAdaptee; // 组合了老接口
public:
virtual void process() {
//...
int data = pAdaptee->bar();
pAdaptee->foo(2);
//...
}
};

define

将一个类的接口转换为客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作

  • Adapter模式主要应用于希望复用一些现纯的类,但是接口又与复用环境要求不一致的情况,在遗留代码复用 类库迁移等方面非常有用

  • GOF23定义了两种Adapter模式的实现结构,对象适配器和类适配器。但类适配器采用多继承的实现方法,一般不推荐使用。对象适配器采用对象组合的方式,更符合松耦合精神

  • Adapter模式可以实现的非常灵活,不必拘泥与GOF23中定义的两种结构。例如,完全可以将Adapter模式中现纯对象作为新的接口方法参数,来达到适配的目的

中介者模式

Motivation

在软件构建过程中,经常会出现多个对象相互关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。这种情况下,我们可以使用一个中介对象来管理对象间的关联关系,避免相互交互对象之间的紧耦合引用关系,从而更好的抵御变化。

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
class Colleague;

//定义一个中介者接口,包含对象改变所需调用函数。
class Mediator {
public :
virtual ~Mediator() = default;
virtual void changed(Colleague *)=0;
};

//定义"同事"类接口,初始化需要一个中介者对象,并通过该类更新另外一个"同事"
class Colleague
{
public :
//初始化中介者类对象
Colleague(Mediator *mediator) { this->mediator = mediator; }
// 更新另外一个类
virtual void changed() { mediator->changed(this); }
private:
Mediator *mediator;
};

//具体的同事类1
class ConcreteColleague1 :public Colleague
{
public:
ConcreteColleague1(Mediator *mediator) : Colleague(mediator) {}
void update() {
cout << "update ConcreteColleague1 from ConcreteColleague2" << endl;
}
};
//具体的同事类2
class ConcreteColleague2 :public Colleague
{
public :
ConcreteColleague2(Mediator *mediator) : Colleague(mediator) {}
void update() {
cout << "update ConcreteColleague2 from ConcreteColleague1" << endl;
}
};

//具体的中介者类,实现更新函数changed。
class ConcreteMediator :public Mediator {
ConcreteColleague1 * colleague1;
ConcreteColleague2 * colleague2;
public:
void setColleague1(ConcreteColleague1 *colleague) { colleague1 = colleague; }
void setColleague2(ConcreteColleague2 *colleague) { colleague2 = colleague; }
ConcreteMediator() {
// colleague1 = new ConcreteColleague1(this);
}
~ConcreteMediator() {}
virtual void changed(Colleague *colleague) {
if (colleague == colleague1) {
colleague2->update();
} else if (colleague == colleague2) {
colleague1->update();
} else
;
}
};
int main()
{
ConcreteMediator concreteMediator;
ConcreteColleague1 colleague1(&concreteMediator);
ConcreteColleague2 colleague2(&concreteMediator);
concreteMediator.setColleague1(&colleague1);
concreteMediator.setColleague2(&colleague2);
//"同事1"通过中介者更新"同事2"
colleague1.changed();
//"同事2"通过中介者更新"同事1"
colleague2.changed();
}

define

用一个中介对象来封装(封装变化)一系列的对象交互。中介者使各对象不需要显式的相互引用(编译时依赖->运行时依赖),从而使其耦合松散,而且可以独立的改变他们之间的交互

  • 将多个对象间复杂的关联关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变多个对象相互关联为多个对象和一个中介者关联,简化来系统的维护,抵御来可能的变化

  • 随着控制逻辑的复杂化,Mediator具体对象的实现可能相当复杂。这时候可以对Mediator对象进行分解处理

  • Facade模式是解耦系统间(单向)的对象关联关系,Mediator模式是解耦系统内各个对象之间(双向)的关联关系

状态变化模式

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

典型模式:

  • State

  • Memento

状态模式

Motivation

在软件构建过程中,某些对象的状态如果改变,其行为也会随之发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。如何在运行时根据对象的状态来透明的更改对象的行为?而不会为对象操作和状态转换之间引入紧耦合。

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
enum class NetWorkState {
Network_Open,
Network_Close,
Network_Connect,
};

class NetworkProcessor {
NetWorkState state;
public:
void Operator1() {
if(state == NetWorkState::Network_Open) {
// ...
state = NetWorkState::Network_Close;
} else if(state == NetWorkState::Network_Close) {
// ...
state = NetWorkState::Network_Connect;
} else if(state == NetWorkState::Network_Connect) {
//...
state = NetWorkState::Network_Open;
}
}

void Operator2() {
if(state == NetWorkState::Network_Open) {
// ...
state = NetWorkState::Network_Connect;
} else if(state == NetWorkState::Network_Close) {
// ...
state = NetWorkState::Network_Open;
} else if(state == NetWorkState::Network_Connect) {
// ...
state = NetWorkState::Network_Close;
}
}
};

当状态增加或发生变化时,我们要修改每一个operator

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
class NetworkState {
public:
NetworkState* pNext;
virtual void Operator1() = 0;
virtual void Operator2() = 0;
virtual ~NetworkState() = default;
};

class OpenState : public NetworkState {
static NetworkState* m_instance;
public:
static NetworkState* getInstance() {
if(m_instance == nullptr) {
m_instance = new OpenState{};
}
return m_instance;
}
void Operator1() override {
// ...
pNext = CloseState::getInstance();
}
void Operator2() override {
// ...
pNext = ConnectState::getInstance();
}
};

class CloseState : public NetworkState {
static NetworkState* m_instance;
public:
static NetworkState* getInstance() {
if(m_instance == nullptr) {
m_instance = new CloseState{};
}
return m_instance;
}
void Operator1() override {
// ...
pNext = CloseState::getInstance();
}
void Operator2() override {
pNext = OpenState::getInstance();
}
};

class ConnectState: public NetworkState {
static NetworkState* m_instance;
public:
static NetworkState* getInstance() {
if(m_instance == nullptr) {
m_instance = new ConnectState{};
}
return m_instance;
}
void Operator1() override {

}
void Operator2() override {

}
};

class NetworkProcessor {
NetworkState* pState;
public:
NetworkProcessor(NetworkState* pState) {
this->pState = pState;
}
void Operator1() {
//...
pState->Operator1();
pState = pState->pNext;
}
void Operator2() {
//...
pState->Operator2();
pState = pState->pNext;
}
};

当增加wait状态时,我们只需要写新的waitState即可。

define

允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为

  • State模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象切换时,切换相应的对象,但同时维持State的接口,这样实现来具体操作与状态转换之间的解耦

  • 为不同状态引入不同的对象使得状态转换变的更加明确,而且可以保证不会出现状态不一致的情况,因为转换是原子性的,即要么彻底转换过来,要么不转换

  • 如何State对象没有实例变量,那么各个上下文可以共享一个State对象,从而节省对象开销

备忘录模式

Motivation

在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需求,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。

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
class Memento {
std::string state;
public:
Memento(const std::string& s) : state(s) { }
std::string getState() const { return state; }
void setState(const std::string& s) { state = s; }
};

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

int main() {
Originator originator;
Memento mem = originator.createMomento(); // 存储到备忘录
// ... originator 状态发生改变
originator.setMomento(mem); // 从备忘录中恢复
}

define

在不破坏封装性的前提下,捕获一个对象内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。

  • 备忘录存储原发器(Originator)对象内部状态,在需要时恢复原发器状态

  • Memento模式的核心是信息隐藏,即Originatro需要向外界隐藏信息,保持其封装性。但同时又需要将状态保持到外界(Memento)

  • 由于现代语言运行时都具有相当的对象序列化支持,因此往往采用效率高又容易正确实现的序列化方案来实现Memento模式

数据结构模式

常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大的破坏组件的复用。这时候,将这些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案

典型模式:

  • Composite

  • Iterator

  • Chain of Resposibility

组件模式

Motivation

在软件在某些情况下,客户代码过多的依赖与对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性,拓展型等弊端。如何将客户代码与复杂的对象容器结构解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?

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
// 树节点
class Component {
public:
virtual void process() = 0;
virtual ~Component() = default;
};

class Composite : public Component {
std::string name;
std::list<Component*> elements;
public:
Composite(const std::string& s) : name(s) { }
void add(Component* element) {
elements.push_back(element);
}
void remove(Component* element) {
elements.remove(element);
}
void process() override {
// 1. process cur nodes
// 2. process leaf nodes
for(auto& e : elements) {
e->process();
}
}
};

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

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

root.add(&treeNode1);
treeNode1.add(&treeNode2);
treeNode2.add(&left1);
root.add(&treeNode3);
treeNode3.add(&treeNode4);
treeNode4.add(&left2);

root.process();
}

define

将对象组合成树形结构以表示”部分 - 整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)

  • Composite 模式采用树形结构来实现普遍存在的对象容器,从而将一对多的关系转化为一对一的关系,使得客户代码可以一致的处理对象和对象容器,无需关系处理的是单个对象,还是组合对象容器

  • 将客户代码与复杂的对象容器结构解耦是Composite的核心思想,解耦后,客户代码将与纯粹的抽象解决(而非对象容器的内部实现结构)发生依赖,从而更能应对变化

  • Composite模式在具体实现中,可以让父对象中的子对象反向追溯,如果父对象有频繁的便利需求,可使用缓存技巧来改善效率

职责链

在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时智能有一个接受者,如果显示指定,将必不可少的带来请求者与接受者的紧耦合。如何使请求的发送者不需要指定具体的接受者?让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。

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
enum class RequeseType {
REQ_HENDLER1,
REQ_HENDLER2,
REQ_HENDLER3
};

class Request {
std::string description;
RequeseType reqType;
public:
Request(const std::string& desc , RequeseType type)
: description(desc) , reqType(type) { }
RequeseType getReqType() const { return reqType; }
const std::string& getDescription() const { return description; }
};

class ChainHandler {
ChainHandler* nextChain;
void sendReqestToNextHandler(const Request& req) {
if(nextChain != nullptr) {
nextChain->handle(req);
}
}
protected:
virtual bool canHandleRequest(const Request& req) = 0;
virtual void processRequest(const Request& req) = 0;
public:
ChainHandler() { nextChain = nullptr; }
void setNextChain(ChainHandler* next) { nextChain = next; }

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

class Headler1 : public ChainHandler {
protected:
bool canHandleRequest(const Request& req) override {
return req.getReqType() == RequeseType::REQ_HENDLER1;
}

void processRequest(const Request& req) override {
std::cout << "Headler1 is handle reqest" << std::endl;
}
};

class Headler2 : public ChainHandler {
protected:
bool canHandleRequest(const Request& req) override {
return req.getReqType() == RequeseType::REQ_HENDLER2;
}

void processRequest(const Request& req) override {
std::cout << "Headler2 is handle reqest" << std::endl;
}
};

class Headler3 : public ChainHandler {
protected:
bool canHandleRequest(const Request& req) override {
return req.getReqType() == RequeseType::REQ_HENDLER3;
}

void processRequest(const Request& req) override {
std::cout << "Headler3 is handle reqest" << std::endl;
}
};

int main() {
Headler1 h1;
Headler2 h2;
Headler3 h3;
h1.setNextChain(&h2);
h2.setNextChain(&h3);
Request req("process task", RequeseType::REQ_HENDLER3);
h1.handle(req);
}

define

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链请求,直到有一个对象处理它为止。

  • Chain of Responsibility 模式的应用场合在于一个请求可能有多个接受者,但是最后真正的接受者只有一个,这时候请求发送者与接受者的耦合有可能出现变化脆弱的症状,责任链的目的就是将二者解耦,从而更好的应对变化

  • 应用来 Chain or Responsibility 模式后,对象的职责分派将更具灵活性。我们可以在运行时动态添加或修改的处理职责

  • 如果请求传递到职责链的末尾仍得不到处理,应该有一个合理的缺省机制。这也是每一个接受对象的责任,而不是发出请求对象的责任

行为变化模式

在组件的构建过程中,组件行为的变化经常导致组件本身剧烈的变化。行为变化模式将组件的行为和组件本身进行解耦,从而支持行为的变化,实现两者之间的松耦合

典型模式:

  • Command

  • Visitor

命令模式

Motivation

在软件构建过程中,行为请求者与行为实现者通常呈现一种紧耦合。但在某些场合,比如需要对行为进行记录 撤销 等处理,这种无法抵御变化的紧耦合是不合适的。这种情况下,如何将行为请求者与行为实现者解耦?这一组行为抽象为对象,可以实现二者的松耦合

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
class Command {
public:
virtual void execute() = 0;
};

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

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

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

define

将一个请求(行为)封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销操作

  • Command 模式的根本目的在于将行为请求者与行为实现者解耦,在面向对象语言中,常见的手段是将行为抽象为对象

  • 实现Command接口的具体命令ConcreteCommand有时候更具需要可能会保存一些额外的状态信息。通过使用Composite模式,可以将多个命令封装为一个复合命令 MacroCommadn

  • Command模式与c++中函数对象有些类似。但二者定义行为接口的规范有所区别。Command以面向对象中接口-实现来定义行为接口规范,更严格,但有失性能,c++函数对象以函数签名来定义行为接口规范,更灵活,性能更高

访问器

Motivation

在软件构建过程中,由于需求的改变,某些层次结构中常常需要增加新的行为,如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有的设计。如何在不更改类层次结构的前提下,在运行时根据需要透明的为类层次结构上各个类动态的添加新的操作,从而避免上述问题

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
class ElementA;
class ElementB;

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

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 Visitor1 : public Visitor {
public:
void visitElementA(ElementA& element) override {
std::cout << "Visitor1 is processing ElementA" << std::endl;
}
void visitElementB(ElementB& element) override {
std::cout << "Visitor1 is processing ElementB" << std::endl;
}
};

class Visitor2 : public Visitor {
public:
void visitElementA(ElementA& element) override {
std::cout << "Visitor2 is processing ElementA" << std::endl;
}
void visitElementB(ElementB& element) override {
std::cout << "Visitor2 is processing ElementB" << std::endl;
}
};

int main() {
Visitor2 visitor;
ElementB element;
element.accept(visitor);
}

define

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

  • Visitor模式通过所谓的双重分发来实现在不更改Element类层次结构的前提下,在运行时透明的为类层次结构上的各个类动态添加新的操作

  • 所谓的双重分发即Visitor模式中间包括两个多态分发,第一个accept方法的多态辨析,第二个为visitElementX方法的多态辨析

  • Visitor模式的最大缺点在于拓展类层次结构(添加新的Element子类),会导致Visitor类的改变。因此Visitor模式适用于Element类层次结构稳定,而其中的操作却经常面临频繁改动

https://zhuanlan.zhihu.com/p/269835115

C++ 设计模式 - 代理模式在某些情况下,客户端代码不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象 - 掘金