简介
设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案,用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
分类
□创建型模式:
◇单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。
□结构型模式:
◇适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
□行为型模式:
◇模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
具体如下图所示:
OOP七大原则
◆开闭原则:对扩展开放,对修改关闭
◆里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立
◆依赖倒置原则:要面向接口编程,不要面向实现编程。
◆单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性。
◆接口隔离原则:要为各个类建立它们需要的专用接口
◆迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话。
◆合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
创建型模式
单例模式(Singleton模式)
最常用的设计模式之一,保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享,即一次创建多次使用。
实现思路
-
私有化它的构造函数,以防止外界创建单例类的对象;
-
使用类的私有静态指针变量指向类的唯一实例;
-
用一个公有的静态方法获取该实例,即提供静态对外接口,可以让用户获得单例对象。
懒汉模式
懒汉式(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
和p2
的地址,发现地址是一样的,说明构造函数只被调用了一次,p1
和p2
是同一个对象。
问题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 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;
|
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) { 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
| 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() 方法会返回一个未定义的实例。