常用设计模式

一、常用设计模式

设计模式是一种解决问题的方案,不同模式的问题抽象出对应的方案。类似于建筑蓝图,设计模式可能没有像算法一样那么详细的描述实施的步骤,只是给出解决问题的指导思想,具体实现细节因人而异。

1、单例

单例模式使用频率很高,比如Qt的QCoreApplication::instance()。单例作用在于保证一个类只创建一个实例,所有用户只能通过特定接口访问唯一的实例,而不能自己创建新的实例。常用于代码里面涉及到管理的类型。代码示例如下:

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
#ifndef SINGLETON_H
#define SINGLETON_H

class Singleton
{
public:
static Singleton *getInstance(int instanceID)
{
if (!m_pSelf) {
m_pSelf = new Singleton(instanceID);
}
return m_pSelf;
}

int getInstanceID()
{
return m_InstanceID;
}
Singleton(const Singleton &) = delete;
void operator =(const Singleton &) = delete;

protected:
Singleton(int data): m_InstanceID(data) {}

private:
static Singleton *m_pSelf;
int m_InstanceID;
};

Singleton *Singleton::m_pSelf = nullptr;
#endif // SINGLETON_H

c++11的静态变量已经是线程安全的,所以不需要加锁了。下面示例演示该方案的单例是否在多线程模式生效。

1
2
3
4
5
6
7
8
auto func = [](int id) {
cout << Singleton::getInstance(id)->getInstanceID() << endl;
};

for (int var = 0; var < 10; ++var) {
std::thread thread1(func, var);
thread1.join();
}

通过运行结果可以看出10个线程只常见了1个实例。

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
#ifndef FACTORY_H
#define FACTORY_H

#include <iostream>

class Product
{
public:
enum {
Type = 0,
UserType = 65535
};
Product() {std::cout << type() << std::endl;}
~Product() {}

protected:
virtual void function()
{
//do something for Base Product
}
virtual int type()
{
return Type;
}
private:
std::string m_productName;
};

class ProductType1: public Product
{
public:
enum {
Type = 1
};
ProductType1() {std::cout << type() << std::endl;}
~ProductType1() {}

protected:
virtual void function() override
{
//do something for type1
}

virtual int type() override
{
return Type;
}
};

class ProductType2: public Product
{
public:
enum {
Type = 2
};
ProductType2() {std::cout << type() << std::endl;}
~ProductType2() {}

protected:
virtual void function() override
{
//do something for type2
}

virtual int type() override
{
return Type;
}
};

class Factory
{
public:
Factory() = delete;
static Product *createProduct(int type)
{
if (type == 0) {
return new Product();
} else if (type == 1) {
return new ProductType1();
} else if (type == 2) {
return new ProductType2();
} else {
return nullptr;
std::cout << "This product not support yet." << std::endl;
}
}
};
#endif // FACTORY_H

使用示例示例:

1
2
3
4
5
6
Product *p1 = Factory::createProduct(1);
Product *p2 = Factory::createProduct(2);
if (p1)
delete p1;
if (p2)
delete p2;

工厂类也可以按照不同产品创建不同工厂,这样的话一个产品就需要一个对应的工厂,为了减少工厂类,所以我采取在同一个工厂根据类型的不同创建不同的产品。

3、抽象工厂模式

抽象工厂模式是工厂模式的扩展,工厂模式只能生产不同的类型的产品,加入同一个产品存在多个风格,这就可以创建一个抽象工厂类,然后派生工厂类创建同一风格的系列产品。按照抽象工厂的思路来说,这就需要每一个风格对应一个派生类工厂,这样的实现我觉得太过于繁琐,所以还是可以参照上述工厂模式的实现,只需要在createProduct接口中扩展一个风格参数即可,同理如果还有分类可以继续增加参数。

4、生成器模式

生成器模式也是工厂模式的扩展,工厂模式只是关注于创建的产品的结果,而生成器模式则是关注于拆分产品创建的流程,复用其中通用的基础操作。比如一栋房子,基础操作打地基,吊顶,装修等。每一栋房子都创建一套自己的建造方式就很费事,如果把这些基础操作拆分,不同类型的房子使用不同的基础操作进行组合就可以建造不同类型的房子。基于这种思路就需要一个建造者(Builder)具有所有的基础操作,然后能够在建造后返回结果。用户可以直接使用建造者去组合操作创建产品,也可以利用一个经理(Manager)去组合固定的操作,让它去指挥建造者,只需要从经理处返回结果即可。代码示例如下:

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
#ifndef BUILDER_H
#define BUILDER_H
class House
{
public:
House() {}
void setFoundation(bool flag)
{
m_hasFoundation = flag;
}
void setRoof(bool flag)
{
m_hasRoof = flag;
}
void setWindowCount(unsigned int count)
{
m_windowCount = count;
}


private:
bool m_hasFoundation;
bool m_hasRoof;
unsigned int m_windowCount;
};

class Builder
{
public:
Builder() {}
virtual ~Builder() {}
virtual void buildFoundation() = 0;
virtual void buildRoof() = 0;
virtual void buildWindow(unsigned int count) = 0;
virtual House *getHouse() = 0;
};

class Builder1: public Builder
{
public:
Builder1(): m_pHouse(nullptr)
{
init();
}
void buildFoundation() override
{
m_pHouse->setFoundation(true);
}
void buildRoof() override
{
m_pHouse->setRoof(true);
}
void buildWindow(unsigned int count) override
{
m_pHouse->setWindowCount(count);
}
~Builder1()
{
if (m_pHouse) {
delete m_pHouse;
m_pHouse = nullptr;
}
}

House *getHouse() override
{
//经过一系列建造获得的最终结果
House *pHouse = m_pHouse;
init();
return pHouse;
}

protected:
void init()
{
m_pHouse = new House;
}

private:
House *m_pHouse;
};

class Manager
{
public:
Manager(Builder *pBuilder)
{
if (pBuilder) {
m_pBuilder = pBuilder;
}
}
~Manager()
{
if (m_pBuilder) {
delete m_pBuilder;
m_pBuilder = nullptr;
}
}

void buildBasicHouse()
{
m_pBuilder->buildFoundation();
m_pBuilder->buildRoof();
m_pBuilder->buildWindow(1);
}

void buildFancyHouse()
{
m_pBuilder->buildFoundation();
m_pBuilder->buildRoof();
m_pBuilder->buildWindow(4);
}

House *getHouse()
{
m_pBuilder->getHouse();
}

private:
Builder *m_pBuilder;
};
#endif // BUILDER_H

5、桥接模式

跨平台的GUI框架,比如Qt之类,我们基本只是使用其中最上层的API,对于这些API是如何跨平台的,用户一般接触不到。这就是桥接模式的作用,将一个复杂的功能,分为抽象层和实现层。抽象层和实现层可以独立进行开发,耦合性很低。要开发维护代码也会更加简单,只需要修改相关的模块即可。这也是利用组合模式的一种扩展。如果将功能集中在一个层次里面实现,随着规模越来越大,维护起来的难度也会越来越大,因为维护者需要对整个功能完全了解才能准确的去修改相关代码,不然会引起连锁反应。

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
#ifndef BRIDGE_H
#define BRIDGE_H
#include <iostream>
using namespace std;
class GuiImplementation
{
public:
GuiImplementation() {}
virtual ~GuiImplementation() {}
virtual void createWinodw() = 0;
};

class GuiImplementationWindows: public GuiImplementation
{
public:
void createWinodw() override
{
cout << "Create window in windows platform " << endl;
}
};

class GuiImplementationLinux: public GuiImplementation
{
public:
void createWinodw() override
{
cout << "Create window in linux platform " << endl;
}
};

class GuiCreator
{
public:
GuiCreator()
{
#ifdef WIN32
m_pImplementation = new GuiImplementationWindows;
#elif define LINUX
m_pImplementation = new GuiImplementationLinux;
#else
m_pImplementation = nullptr;
#endif
}
~GuiCreator()
{
if (m_pImplementation) {
delete m_pImplementation;
m_pImplementation = nullptr;
}
}
void createWindow()
{
if (m_pImplementation) {
m_pImplementation->createWinodw();
} else {
cout << "This platform has not implemented!" << endl;
}
}

private:
GuiImplementation *m_pImplementation;
};
#endif // BRIDGE_H

实现和抽象的组合,我这里为了用户方便使用,直接通过宏定义来在编译期确定,某些情况下没有这些可利用的宏定义,那么可以把实现类作为抽象类的构造函数参数传递进来,具体使用哪个实现类由用户自己决定。

6、观察者模式

现实中观察者模式的例子非常多,比如一个商城,上面有一些货物预售,但是不知道什么时候会上架,这时候顾客只能每天重复去查看该商品是否上架,顾客会觉得繁琐,或者商店在到货后通知所有人,不关心的顾客会觉得被骚扰。这时候通常是采取订阅的方式,只有订阅过的用户才会收到到货通知。

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
#ifndef SUBSCRIPTION_H
#define SUBSCRIPTION_H

#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
class ISubscriber
{
public:
virtual ~ISubscriber() {}
virtual void update(const string &message) = 0;

};

class ISubject
{
public:
virtual ~ISubject() {}
virtual void notify() = 0;
virtual void addMember(ISubscriber *) = 0;
virtual void removeMember(ISubscriber *) = 0;
};



class Subscriber: public ISubscriber
{
public:
Subscriber(ISubject *pSubject): m_pSubject(pSubject)
{
++m_staticID;
m_ID = m_staticID;
m_pSubject->addMember(this);
}
~Subscriber()
{
m_pSubject->removeMember(this);
}

void update(const string &messages)
{
cout << "ID:" << m_ID << " Received a new message: " << messages << endl;
}

private:
static int m_staticID;
int m_ID;
ISubject *m_pSubject;
};

int Subscriber::m_staticID = 0;

class Subject: public ISubject
{
public:
Subject(): m_message("")
{

}
~Subject()
{
cout << "Subject destroyed" << endl;
}

void addMember(ISubscriber *pSubcriber) override
{
m_subcriberList.emplace_back(pSubcriber);
}
void removeMember(ISubscriber *pSubcriber) override
{
m_subcriberList.remove(pSubcriber);
}
void notify() override
{
for (auto pSubscriber : m_subcriberList) {
pSubscriber->update(m_message);
}
}
void generateNewMessage(const string &message)
{
if (message == m_message) {
return;
} else {
m_message = message;
notify();
}
}
private:
std::list<ISubscriber *> m_subcriberList;
string m_message;

};
#endif // SUBSCRIPTION_H

以上代码适合简单的消息订阅,如果复杂的情况下,可能消息的类型会有很多,比如窗口会有点击事件,绘制事件等,可以采用继承的方式封装一个基类消息,其他消息继承该基类。同时发布消息的对象也可能有很多,这时候可以把消息的发布和通知拆分出来,发布者单纯只是发布消息,具体消息的转发由一个集中的事件分发器去执行,比如Qt的application。


常用设计模式
http://yoursite.com/2021/03/13/常用设计模式/
作者
还在输入
发布于
2021年3月13日
许可协议