0%

设计模式

设计原则

依赖倒置原则(DIP)

高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖 于抽象(稳定)

抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于 抽象(稳定)

开放封闭原则(OCP)

对扩展开放,对更改封闭

类模块应该是可扩展的,但是不可修改

模版方法(Template Method)

模式动机

一些功能在底层开发模块时不知具体实现,而需要在高层模块实现

模式定义

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

实现方法

由高层模块实现具体方法,其继承自包含该方法的基类。低层模块调用该基类upcast后的函数来执行

单例模式(Singleton Pattern)

模式动机

基于性能和软件正确性方面的考量,某些类只允许创建一次

模式定义

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

实现方法

第一次调用创建新对象,当创建了该对象后再次调用则返回已创建的对象

具体例子(推荐的实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Singleton.cpp
class Singleton{
private:
Singleton();
Singleton(const Singleton& other);

public:
Singleton* instance;
static Singleton* getInstance() {
if (instance == nullptr) { // 双重检查锁定(线程安全)
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr)
instance = new Singleton();
}
return instance;
}
};

工厂方法(Factory Method)

模式动机

在创建对象的时候不确定其具体类型,无法通过new来创建对象

模式定义

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

实现方法

定义工厂类用于创建对象。创建一个工厂基类,不同的具体工厂类继承该基类。在低层模块创建对象时调用基类指针upcast之后的具体工厂的具体类创建方法。将不稳定隔离在上层,由上层传递具体工厂的对象给下层

具体例子

不好的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
// MainForm.cpp
class MainForm : public Form
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;

public:
void Button1_Click(){
ISplitter * splitter = new BinarySplitter();//依赖具体类
splitter->split();
}
};

推荐的实现

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
// MainForm.cpp
class MainForm : public Form
{
SplitterFactory* factory;//工厂
public:
MainForm(SplitterFactory* factory){
this->factory=factory;
}

void Button1_Click(){
ISplitter * splitter = factory->CreateSplitter(); //多态new
splitter->split();
}
};

// Factory.cpp
//工厂基类
class SplitterFactory{
public:
virtual ISplitter* CreateSplitter()=0;
virtual ~SplitterFactory(){}
};

//具体工厂
class BinarySplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new BinarySplitter();
}
};

class TxtSplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new TxtSplitter();
}
};

观察者模式(Observer Pattern)

模式动机

建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应

模式定义

定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新

实现方法

由订阅者实现具体的通知反应方法,其继承自通知基类。发布者在发送通知时调用该基类upcast后的函数来执行通知流程

具体例子

实现一个文件切分的进度条通知机制

不好的实现

在本实现中,由切分器实现具体的通知方法。存在的问题是切分器应该是较为稳定的模块,如果跟换了进度通知方法(例如控制台输出等)则需要修改split方法

该实现违反了依赖倒置原则(高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖 于抽象(稳定))

由于切分器是较为稳定的模块(稳定),而UI界面才是与进度展示方式相绑定模块(不稳定)。该法将不稳定的通知实现绑定在了稳定的切分器模块,所以不是一个好的实现

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
// FileSplitter.cpp(切分器)
class FileSplitter
{
string m_filePath;
int m_fileNumber;
ProgressBar* m_progressBar; //!!!违反了依赖倒置原则,具体通知控件

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

}
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
m_progressBar->setValue(progressValue);
}
}

// MainForm.cpp(UI界面)
class MainForm : public Form
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;

public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath, number, progressBar);
splitter.split();
}
};

推荐的实现

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
// FileSplitter.cpp(切分器)
class IProgress{ // 抽象的通知机制,由界面自己实现通知展示方法
public:
virtual void DoProgress(float value)=0;
virtual ~IProgress(){}
};

class FileSplitter
{
string m_filePath;
int m_fileNumber;
List<IProgress*> m_iprogressList; // 抽象通知机制,支持多个观察者
public:
FileSplitter(const string& filePath, int fileNumber) :
m_filePath(filePath),
m_fileNumber(fileNumber){
}

void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
onProgress(progressValue);//发送通知
}
}

void addIProgress(IProgress* iprogress){
m_iprogressList.push_back(iprogress);
}

void removeIProgress(IProgress* iprogress){
m_iprogressList.remove(iprogress);
}


protected:
virtual void onProgress(float value){
List<IProgress*>::iterator itor=m_iprogressList.begin();
while (itor != m_iprogressList.end() )
(*itor)->DoProgress(value); //更新进度条
itor++;
}

// MainForm.cpp(UI界面)
class MainForm : public Form, public IProgress//继承IProgress 接口
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
ConsoleNotifier cn;
FileSplitter splitter(filePath, number);
splitter.addIProgress(this); //订阅通知
splitter.addIProgress(&cn); //订阅通知
splitter.split();
splitter.removeIProgress(this);
}

virtual void DoProgress(float value){
progressBar->setValue(value);
}
};

class ConsoleNotifier : public IProgress {
public:
virtual void DoProgress(float value){
cout << ".";
}
};

观察者模式 VS 发布订阅模式(Publish–Subscribe pattern)

发布订阅模式(架构模式)中,发布者并不知道订阅者的存在,只是把消息发送给broker。订阅者通过订阅相关的topic获取消息。如下图所示;而在观察者模式(设计模式)中,发布者会通知订阅者,只不过具体的通知操作由订阅者自己实现

特性 观察者模式 发布-订阅模式
耦合度 被观察者和观察者之间耦合度较高 发布者和订阅者之间完全解耦
通信方式 直接通信(被观察者调用观察者的方法) 间接通信(通过消息代理)
灵活性 适合固定的、一对多的依赖关系 适合动态的、多对多的消息传递
复杂性 实现简单 实现复杂,需要引入消息代理
适用场景 对象状态变化需要通知其他对象 需要高度解耦的分布式系统或事件驱动系统