单例模式在我的理解中,应该算是设计模式里面最简单的一种设计模式,它最主要的作用就像模式的名称一样,防止一个类被多次实例化。
在项目中,我们往往会遇到下面的情况:
1、应用需要访问数据库,但是建立连接和断开连接都需要巨大的开销,但一般的增删改查的性能消耗反而没那么高。而应用则需要频繁访问数据库。
2、共享内存需要互斥访问,而那些原子操作的函数又比较复杂,不敢让一些新手来使用,希望能够将这些操作封装起来,暴露一些简单的访问操作。
3、配置文件中存放了各个模块需要的一些配置数据,这些数据需要在各个模块中访问,有些模块可能需要访问同一个配置项,但是模块之间却希望能够独立,同时,也不希望一份数据在内存中有多个拷贝。
我在解决这些问题的时候,往往就会引入单例这种设计模式。
设计模式是为了应对需求的变动而产生的,但是单例在很大程度上却并不是为了应对需求的变动,而是为了应对资源访问控制的。单例模式,顾名思义,就是在一个进程空间内,一个类有且只有一个对象。单例很像一个全局变量,因此,很多的面向对象的语言都把全局变量取消了,比如JAVA、C#。那么单例是如何应对上述的三种情况的呢?
1、数据库的访问,在初次访问甚至是在应用启动的时候,就建立连接,之后不再主动断开,直至应用退出。这样可以大大减少大开销的操作,进而提升性能。
2、将共享内存的内存块作为类成员变量,而那些原子操作则封装为成员函数,暴露简单的访问功能,这样,新手程序员不需要知道核心的实现逻辑,只需要调用简单的访问接口即可。
3、将一类配置文件的配置项集合起来,作为一个单例成员变量,暴露一个加载配置的方法用以一次性的加载,再暴露每个配置项的访问接口即可。
单例长成什么样子的呢?基本的类图如下:
单例类的核心在于需要将所有可能造成新构造一个对象的函数的访问权限设定为私有,这些函数包括:默认构造函数、拷贝构造函数、赋值操作函数。然后暴露一个静态的公有函数(图中的GetInstance)获取类的唯一对象。
那么GetInstance应该怎么写呢?如果不考虑多线程,GetInstance会非常简单,参考的代码如下:
static Singleton & GetInstance()
{
static Singletoninstance;
return instance;
}
函数利用了静态变量的全局唯一性,在无需考虑多线程加锁的情况下,instance这个静态变量在首次访问的时候会被初始化。
这种初始化的模式也被称之为懒汉模式,即在最晚的时候进行初始化。既然有懒汉模式,那就必然也会有另一种模式,可惜我没找到一个比较标准命名。这种模式下的单例的类图如下所示:
这种模式和懒汉模式相比,区别主要在于将实例的静态变量放到了类中,这样的话,我们先要在应用启动的时候,对静态变量进行初始化,样例代码如下:
// cpp
Singleon Singleton::instance();
这样的话,应用程序在启动的时候就会对instance进行初始化,而不是等到首次访问的时候。这种情况下,GetInstance的写法相对更简单,参考代码如下:
static Singleton & GetInstance()
{
return instance;
}
上述的单例的设计中,不管是否是懒汉模式,如果调用点希望用一个变量来存放GetInstance返回的引用,那么变量的声明必须要是一个引用,而不能是一个对象。即:
Singleton & instance = Singleton::GetInstance();
上面这种方法不会有问题,而如果是下面这种方法,则在编译的时候会报错
Singleton instance = Singleton::GetInstance();
报错的原因就是因为operator=这个操作符被定义为了私有。也就是说,如果我们平时对于对象使用下面这种方式进行赋值的话,其实会调用一次operator =,如果是上面这种,则不会。
上述的代码都不考虑是否线程安全,因此,多线程下的单例模式的代码可以去网上找找。
上述的单例模式还有一个缺陷,那就是不能防止通过继承来创建多个实例,这个缺陷可以通过增加关键字final(C++11、JAVA)、sealed(C#),来确保这个类已经不能再被继承了。
单例模式就说到这儿,新的一年祝大家工作顺利,开开心心。
PS:下次的文章不知道啥时候发^_^。
本文来自企鹅号 - johdan媒体
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文来自企鹅号 - johdan媒体
如有侵权,请联系 cloudcommunity@tencent.com 删除。