zmqNut

明知会散落, 仍不惧盛开

0%

设计模式简解

简介

设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案,用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。

分类

创建型模式

​ ◇单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。

结构型模式

​ ◇适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

行为型模式

​ ◇模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。

具体如下图所示:

image-20220621221705631

OOP七大原则

开闭原则:对扩展开放,对修改关闭

里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立

依赖倒置原则:要面向接口编程,不要面向实现编程。

单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性。

接口隔离原则:要为各个类建立它们需要的专用接口

迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话。

合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。


创建型模式

单例模式(Singleton模式)

最常用的设计模式之一,保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享,即一次创建多次使用。

实现思路

  1. 私有化它的构造函数,以防止外界创建单例类的对象;

  2. 使用类的私有静态指针变量指向类的唯一实例

  3. 用一个公有的静态方法获取该实例,即提供静态对外接口,可以让用户获得单例对象。

懒汉模式

懒汉式(Lazy-Initialization)的方法是直到使用时才实例化对象,也就说直到调用getInstance() 方法的时候才 new 一个单例的对象。好处是如果未被调用就不会占用内存。(实例的初始化放在getInstance函数内部)

最基础版本的单例模式(线程不安全)

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 singleton{
private:
//私有化构造函数
singleton(){}
~singleton(){}

//私有化拷贝构造和赋值构造函数, 禁止外部拷贝和赋值
singleton(const singleton &) = delete;
singleton& operator=(const singleton &) = delete;

//私有静态指针变量指向唯一实例
static singleton* _pInstance;

public:
//公有静态方法获取实例
static singleton* getInstance() {
if(NULL == _pInstance) { //保证只创建一个,保证只创建一个,没有加锁,线程不安全,当线程并发时会创建多个实例
_pInstance = new singleton();
}
return _pInstance;
}
};
//静态成员必须初始化
singleton* singleton::_pInstance = nullptr;

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(){

singleton *p1 = singleton::getInstance();
singleton *p2 = singleton::getInstance();
cout << "&p1 = " << p1 << endl;
cout << "&p2 = " << p2 << endl;

if (p1 == p2)
cout << "same" << endl;

return 0;
}
/*输出:
&p1 = 0xe31750
&p2 = 0xe31750
same
*/

打印p1p2的地址,发现地址是一样的,说明构造函数只被调用了一次,p1p2是同一个对象。

问题1:上面最基础版本的单例模式是存在问题的:

内存泄漏,注意到类中只负责new出对象,却没有负责delete对象,因此只有构造函数被调用,析构函数却没有被调用。因此会导致内存泄漏。我们可以直接通过delete p1;去销毁,但是我们是通过静态方法创建的对象,最好也是用静态的方法销毁,所以必须让这条语句编译不通过。由于对象在销毁的时候会调用析构函数,所以,我们可以把析构函数设置成私有的。然后在类内部定义一个destory()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class singleton{
...
public:
//销毁函数
static void destory() {
if(_pInstance) {
delete _pInstance; //会调用析构函数
_pInstance = NULL;
cout<<"delete Instance"<<endl;
}
}
...
};
int main() {
...
//手动释放
singleton::destory();
...
}

问题2:线程安全问题:当多线程获取单例的时候有可能引发竞争态现象,第一个条件在判断if(NULL == _pInstance)的时候就开始实例化单例,同时第 2 个线程也开始获取这个单例,这个时候_pInstance还是空的,于是也开始实例化单例,这样就会实例化出两个单例对象,这样就会出现线程安全问题了。解决方法:加锁

经典的线程安全懒汉模式,使用双检测锁模式NULL == _pInstance检测了两次)

  • 实现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
class singleton{
private:
//静态锁,是由于静态函数只能访问静态成员
static pthread_mutex_t lk;

//私有化构造函数
singleton(){
pthread_mutex_init(&lk, NULL);
}
~singleton(){}

//私有化拷贝构造和赋值构造函数, 禁止外部拷贝和赋值
singleton(const singleton &) = delete;
singleton& operator=(const singleton &) = delete;

//私有静态指针变量指向唯一实例
static singleton* _pInstance;

public:
//公有静态方法获取实例
static singleton* getInstance() {
if(NULL == _pInstance) {
pthread_mutex_lock(&lk);
if (NULL == _pInstance)
_pInstance = new singleton();
pthread_mutex_unlock(&lk);
}
return _pInstance;
}
};
//静态成员必须初始化
pthread_mutex_t singleton::lk;
singleton* singleton::_pInstance = nullptr;
  • 实现2(基于C++11)
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
class singleton{
private:
//静态锁,是由于静态函数只能访问静态成员
static mutex mtx;

//私有化构造函数
singleton(){}
~singleton(){}

//私有化拷贝构造和赋值构造函数, 禁止外部拷贝和赋值
singleton(const singleton &) = delete;
singleton& operator=(const singleton &) = delete;

//私有静态指针变量指向唯一实例
static singleton* _pInstance;

public:
//公有静态方法获取实例
static singleton* getInstance() {
if(NULL == _pInstance) {
// lock_guard<mutex> lk(mtx);
unique_lock<mutex> lk(mtx);
if (NULL == _pInstance)
_pInstance = new singleton();
lk.unlock();
}
return _pInstance;
}
};
//静态成员必须初始化
mutex singleton::mtx;
singleton* singleton::_pInstance = nullptr;

利用局部静态变量实现线程安全懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// C++11以后,使用局部变量懒汉不用加锁
class singleton{
private:
//私有化构造函数
singleton(){}
~singleton(){}

//私有化拷贝构造和赋值构造函数, 禁止外部拷贝和赋值
singleton(const singleton &) = delete;
singleton& operator=(const singleton &) = delete;

public:
static singleton* getInstance(){
static singleton obj;
return &obj;
};

};

这个方法是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性。

如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。

这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。

饿汉模式

即迫不及待,在程序运行时立即初始化(实例的初始化放在getInstance函数外部,getInstance函数仅返回该唯一实例的指针)

饿汉模式不需要锁,就可以实现线程安全。原因在于,在程序运行时就定义了对象,并对其初始化。之后,不管哪个线程调用成员函数getInstance(),都不过是返回一个的对象的指针而已。所以是线程安全的,不需要在获取实例的成员函数中加锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class singleton{
private:
//私有化构造函数
singleton(){}
~singleton(){}

//私有化拷贝构造和赋值构造函数, 禁止外部拷贝和赋值
singleton(const singleton &) = delete;
singleton& operator=(const singleton &) = delete;

//私有静态指针变量指向唯一实例
static singleton* _pInstance;

public:
static singleton* getInstance(){
return _pInstance;
};

};
singleton* singleton::_pInstance = new singleton();

饿汉模式虽好,但其存在隐藏的问题,在于非静态对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例

---------- End~~ 撒花ฅ>ω<*ฅ花撒 ----------