C++中的一些设计模式
前言
今天来学习一下C++的一些设计模式,本人只学了几个,甚至于我只用过一个,但这不妨碍我去理解这些设计模式,其他的设计模式以后会慢慢补充哒,废话不多说,直接来吧!
设计模式
设计模式(英语 design pattern)是对面向对象设计中反复出现的问题的解决方案
这个术语是在1990年代由Erich Gamma等人从建筑设计领域引入到计算机科学中来的。这个术语的含义还存有争议。算法不是设计模式,因为算法致力于解决问题而非设计问题。设计模式通常描述了一组相互紧密作用的类与对象。设计模式提供一种讨论软件设计的公共语言,使得熟练设计者的设计经验可以被初学者和其他设计者掌握。设计模式还为软件重构提供了目标
随着软件开发社群对设计模式的兴趣日益增长,已经出版了一些相关的专著,定期召开相应的研讨会,而且Ward Cunningham为此发明了WikiWiki用来交流设计模式的经验。
总之,设计模式就是为了解决某类重复出现的问题而出现的一套成功或有效的解决方案
单例模式
在烂大街的WebServer项目中,我们经常利用单例模式来创建对象,日志,数据库连接池等等这种只需要一个对象的情况下,我们就可以使用单例模式来创建对象
什么是单例模式
单例模式就是指在内存中只会创建且仅创建一个对象的设计模式。当我们在程序中多次使用同一个对象且作用相同时(比如日志对象,我们总是往里面写入数据),为了防止频繁的创建对象而使得内存飙升,我们就可以使用单例模式只创建一个对象,所有程序都是用这一个对象
单例模式的类型
- 懒汉模式:顾名思义,就是在程序需要使用这个对象的时候才创建
- 饿汉模式:当类加载的时候,无论该程序中是否会需要使用到该对象,都会创建,就像一个很饿的人,总是想快点吃
懒汉模式
懒汉式创建对象的方法是在**程序使用对象前,先判断该对象是否已经实例化(判空)**,若已实例化直接返回该类对象,否则则先执行实例化操作
在写懒汉模式的单例时,我们需要注意以下几个点:
- 全局只有一个实例
我们知道,在C++中,如果我们给类中的成员变量加上static关键字后,那么该成员变量就会变成全局唯一的静态变量,所有通过该类创建出来的实例都共享同一个变量,并且静态成员变量需要在类外进行初始化
- 将类的构造函数私有化,为的就是不让其他程序利用构造函数重新构造一个新的实例
- 静止赋值和拷贝,所以需要将默认拷贝函数也delete掉
- 用户只能通过类中的成员函数获取唯一实例,由于实例为静态成员变量,所以获取函数必须也是静态成员函数,因为只有静态成员函数才能获取静态成员变量
以下是一个有缺陷但逻辑是对的懒汉模式
1 | class single |
上面的函数在单线程的时候是没有问题的,但是在多线程的时候就出现问题了,也就是老生常谈的线程安全问题,当有三个线程都掉了GetInstance函数的时候,就会创建出三个实例,这是不符合单例模式的定义的,那么如何解决这个问题呢?还是利用很经典的互斥锁
以下是修改完的代码
1 | class single |
但是这样也有一个问题,那就是每次调用都要上锁,但事实上,只要创建好了单例,那么其余线程都会满足p==NULL,所以我们再做一次更新
1 | class single |
饿汉模式
和懒汉模式不同,懒汉模式只有在用户调用GetInstance函数的时候,才会去判断并且创建,而饿汉模式则是当我们类编译的时候,就进行单例的创建,代码如下
1 | class single |
饿汉模式虽好,但其存在隐藏的问题,在于非静态对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。如果在初始化完成之前调用 GetInstance()函数会返回一个未定义的实例
工厂模式
当我们在一个程序中定义了很多类的时候,如果我们频繁的去创建不同的类对象的时候骂我们需要一直new对象,这是很不利于我们后期对代码的维护的,那么有没有一种方法,就是可以自动帮我们创建类对象呢?
来了!它来了!工厂模式就是设计来帮我们解决这个问题的
什么是工厂模式
很简单,举个例子,在海贼王中存在着一个人造恶魔果实(smile果实)制造工厂,当我们想要什么样的恶魔果实,我们告诉工厂就行了,具体如何去创造就是工厂的事情了,不需要我们去关心,这就是工厂模式。利用工厂模式,我们可以不用关心对象的创作过程,我们只需要关心对象的实际操作,也就是说,工厂模式帮助我们分离了对象的创建和使用,方便后期的维护和扩展.
工厂模式也分为三种:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
简单工厂模式
简单工厂模式(Simple Factory Pattern)专门定义一个类来负责创建其他类的实例,被创建的实例通常具有共同的父类
- Factory工厂角色(工厂类):
工厂角色即工厂类,是简单工厂模式的核心,负责创建所有实例的内部逻辑,工厂类可以被外界直接调用,创建所需要的产品对象 - Product(抽象产品角色):
抽象产品角色是简单工厂模式所创建的所有对象的父类,负责描述所有实例所共有的公告接口。所创建的具体产品对象都是其子类对象 - ConcreteProduct(具体产品角色):
具体产品角色是简单工厂模式的创建目标。每个具体产品角色都继承了抽象产品角色,需要实现定义在抽象产品中的方法ProductA、ProductB和ProductC继承自Product虚拟类,Show方法是不同产品的自描述;Factory依赖于ProductA、ProductB和ProductC,Factory根据不同的条件创建不同的Product对象
简单工厂的实现
我们以电视机的栗子来说吧
有一个工厂,它专门就是用来生产电视机的,但我们知道,电视机有很多牌子,不同的牌子价格和质量也是不同的
如下图所示: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
using namespace std;
typedef enum ProductTypeTag
{
Hair,
Hisense,
}PRODUCTTYPE;
//抽象产品类 TV(电视机类)
class TV
{
public:
virtual void Show() = 0;
virtual ~TV(){};//声明析构函数为虚函数,防止内存泄漏
};
//具体产品类 HairTV(海尔电视类)
class HairTV : public TV
{
public:
void Show()
{
cout<<"I'm HairTV "<<endl;
}
};
//具体产品类 HisenseTV(海信电视类)
class HisenseTV : public TV
{
public:
void Show()
{
cout<<"I'm HisenseTV"<<endl;
}
};
// 工厂类 TVFactory(电视机工厂类)
class TVFactory
{
public:
TV* CreateTV(PRODUCTTYPE type)
{
switch (type)
{
case Hair:
return new HairTV();
case Hisense:
return new HisenseTV();
default:
return NULL;
}
}
};
int main(int argc, char *argv[])
{
// 创建工厂类对象
TVFactory* myTVFactory = new TVFactory();
TV* hairTV = myTVFactory->CreateTV(Hair);
if (hairTV != NULL)
hairTV->Show();
TV* hisenseTV = myTVFactory->CreateTV(Hisense);
if (hisenseTV != NULL)
hisenseTV->Show();
delete myTVFactory;
myTVFactory = NULL;
delete hairTV;
hairTV = NULL;
delete hisenseTV;
hisenseTV = NULL;
return 0;
}
优势
本着高内聚低耦合的原则,将系统的逻辑部分和功能分开
劣势
就拿上面的栗子来说吧,当我们想要生产派大星牌电视机的时候,我们就需要在工厂里面加入对派大星牌电视机的生产,但其实这就违背了我们设计模式中的一个开闭原则
开闭原则:
- 开放封闭原则(OCP,Open For Extension, Closed For Modification Principle)
- 类的改动是通过增加代码进行的,而不是修改源代码
- 说的是代码扩展性问题——对扩展开放,对修改关闭(封闭)
开闭原则详细的解释:当增加新功能,不应该通过修改已经存在的代码来进行,而是应该通过扩展代码(比如增加新类,增加新成员函数)来进行
而当我们增加一个新的类时候,就需要对已经存在的工厂代码进行修改,而这就违背了这个原则
工厂方法模式
既然简单工厂模式违背了开闭原则,那么我们就在它的基础上做一些修改,使得改模式即保留了简单工厂模式的优点,又解决了它的缺点,而这,就是工厂方法模式
工厂方法模式
在工厂模式中,工厂父类负责定义创建产品对象的公告接口,而工厂子类负责生成具体的产品对象。
目的是将产品的实例化操作延迟到工厂子类中完成,通过工厂子类来确定究竟应该实例化哪一个具体产品类
Product(抽象产品)
抽象产品是定义产品的接口,是工厂方法模式所创建对象的超类型,也是产品对象的共同父类或接口ConcreteProduct(具体产品)
具体产品实现了抽象产品的接口,某种类型的具体产品由专门的具体工厂创建Factory(抽象工厂)
ConcreteFactory(具体工厂)
具体工厂是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户调用,返回一个具体产品类的实例
简单工厂的实现
还是继续上面的栗子
工厂方法模式,将原有的工厂进行分割,为每种品牌的电视机提供一个子工厂,海尔工厂专门负责生产海尔电视机,海信工厂专门负责生产海信电视机,若增加派大星电视,只需要增加一个新的派大星工厂
1 | {% asset_img 工厂模式1.jpg 工厂模式 %} |
当我们想要生产派大星电视机的时候,只需要加一个工厂和一个具体的派大星电视机类就好,而无需在原本的代码上进行修改
抽象工厂模式
抽象工厂模式是工厂方法模式的泛化版,工厂模式是一种特殊的抽象工厂模式,在工厂模式中,每个具体工厂只能生产一种具体的产品,如海尔电视机厂只生产海尔电视机,而抽象工厂方法模式中,一个具体的工厂可以生产多个具体产品
- AbstractFactory(抽象工厂)
抽象工厂用于声明生成抽象产品的方法,在一个抽象工厂中可以定义一组方法,每一个方法对应一个产品等级结构 - ConcreteFactory(具体工厂)
具体工厂实现了抽象工厂声明的抽象产品的方法,生成一组具体产品 - AbstractProduct(抽象产品)
抽象产品为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法 - ConcreteProdunct(具体产品)
抽象工厂实现
还是上面的栗子,我们知道,海尔公司不光生产电视,它还生产空调之类的,tcl也是
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
using namespace std;
// 抽象产品类类 Television(电视机类)
class Television
{
public:
virtual void Show() = 0;
virtual ~Television(){};//析构函数声明为虚函数,防止内存泄漏
};
//具体产品类 HaierTelevision(海尔电视机类)
class HaierTelevision : public Television
{
public:
void Show()
{
cout << "I'm HaierTelevision" << endl;
}
};
//具体产品类 TCLTelevision(TCL电视机类)
class TCLTelevision : public Television
{
public:
void Show()
{
cout << "I'm TCLTelevision" << endl;
}
};
// 抽象产品类 AirCondition(空调类)
class AirCondition
{
public:
virtual void Show() = 0;
virtual ~AirCondition(){};//析构函数声明为虚函数,防止内存泄漏
};
//具体产品类 HairAirCondition(海尔空调类)
class HairAirCondition : public AirCondition
{
public:
void Show()
{
cout << "I'm HairAirCondition" << endl;
}
};
//具体产品类 TCLAirCondition(TCL空调类)
class TCLAirCondition : public AirCondition
{
public:
void Show()
{
cout << "I'm TCLAirCondition" << endl;
}
};
// 抽象工厂类 EFactory(电器工厂类)
class EFactory
{
public:
virtual Television* CreateTelevision() = 0;
virtual AirCondition* CreateAirCondition() = 0;
virtual ~EFactory(){};//析构函数声明为虚函数,防止内存泄漏
};
//具体工厂类 HairFactory(海尔工厂类)
class HairFactory : public EFactory
{
public:
Television* CreateTelevision()
{
return new HaierTelevision();
}
AirCondition* CreateAirCondition()
{
return new HairAirCondition();
}
};
//具体工厂类 TCLFactory(TCL工厂类)
class TCLFactory : public EFactory
{
public:
Television* CreateTelevision()
{
return new TCLTelevision();
}
AirCondition* CreateAirCondition()
{
return new TCLAirCondition();
}
};
int main(int argc, char *argv[])
{
EFactory *hairFactory = new HairFactory ();/*实例化工厂抽象类*/
Television *haierTelevision =hairFactory->CreateTelevision();/*实例化产品抽象类*/
AirCondition *haierAirCondition = hairFactory->CreateAirCondition();
haierTelevision->Show();
haierAirCondition->Show();
EFactory *tCLFactory = new TCLFactory ();
Television *tCLTelevision = tCLFactory->CreateTelevision();
AirCondition *tCLAirCondition = tCLFactory->CreateAirCondition();
tCLTelevision->Show();
tCLAirCondition->Show();
if (hairFactory != NULL)
{
delete hairFactory;
hairFactory = NULL;
}
if (haierTelevision != NULL)
{
delete haierTelevision;
haierTelevision= NULL;
}
if (tCLAirCondition != NULL)
{
delete tCLAirCondition;
tCLAirCondition = NULL;
}
if (tCLFactory != NULL)
{
delete tCLFactory;
tCLFactory= NULL;
}
if (tCLTelevision != NULL)
{
delete tCLTelevision;
tCLTelevision = NULL;
}
if (tCLAirCondition != NULL)
{
delete tCLAirCondition;
tCLAirCondition = NULL;
}
}
优点
- 抽象工厂模式将产品族的依赖与约束关系放到抽象工厂中,便于管理
- 职责解耦,用户不需要关心一堆自己不关心的细节,由抽象工厂来负责组件的创建
- 切换产品族容易,只需要增加一个具体工厂实现,客户端选择另一个套餐就可以了
优点
- 抽象工厂模式类增加的速度很快,有一个产品族就需要增加一个具体工厂实现,比较繁琐
- 产品族难以扩展产品。当产品族中增加一个产品时,抽象工厂接口中需要增加一个函数,对应的所有具体工厂实现都需要修改,修改放大严重
- 抽象工厂并未完全屏蔽创建细节,给出的都是组件。对于这种情况可以结合工厂模式或简单工厂模式一起使用
观察者模式
观察者模式又叫做发布-订阅模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。
这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动的更新自己。
或许观看以下实例或更清楚点(结构上图)
观察者模式的实现
Head.h
1 |
|
Head.cpp
1 |
|
main.cpp
1 |
|
- 服务器发布新版本,让客户端更新
- 游戏群发邮件给补贴等
- 聊天室系统
都可以很好的套用这种模式
结语
鼠鼠终于!终于又收到了一个面试!虽然不是大厂的,但聊胜于无啊,希望今晚能顺利叭,写完了这篇博客就要看八股去咯,祝好运~
- 标题: C++中的一些设计模式
- 作者: 这题超纲了
- 创建于: 2023-03-10 13:30:59
- 更新于: 2023-06-23 14:39:36
- 链接: https://qx-gg.github.io/2023/03/10/blog11/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。