[设计模式之禅读书笔记]007_23种设计模式一:单例模式
序言
今天开始学习23种设计模式的第一个模式——单例模式,以前在网上也看过不少人写的单例模式博文,也算已经吸收了不少。今天看《设计模式之禅》里对设计模式的讲解,作者说的还挺细致的。唯一可惜的是作者用java讲解单例模式,而我自己从去年工作后就用的是C++,本着学什么用什么的原则,我就用C++来记录一下学习成果吧。希望这篇博客能把我学到的分享给跟我一样在园子的读者。
正文
1. 单例模式的现实场景
作者开篇用皇帝来类比单例,而我有个更好的例子,且听我一一道来。太阳系中有一个恒星和九大行星,至少我上学的时候是九大行星,后来因为冥王星太小了,被除名了。可是我还是觉得九这个数字比较好,比如九大行星,九星连珠,说起来爽口。扯远了,单例模式在这太阳系中相当于什么呢?太阳。是的,太阳。地球说,我要阳光;金星说,我要阳光。这些阳光都来自同一个太阳,也就是说关于太阳这个类的对象,整个太阳系只有一个。这就是单例模式,无论我在太阳系的哪个行星上,我得到的阳光都是来自同一个太阳的。
2. 单例模式实现(一)——非同步单例模式、懒汉式单例
1 #include <iostream> 2 using namespace std; 3 4 class Singleton{ 5 public: 6 static Singleton* getInstance(){ 7 if(instance == NULL){ 8 instance = new Singleton(); 9 } 10 return instance; 11 } 12 private: 13 static Singleton *instance; 14 Singleton(){}; // 使得该类不能在外部初始化 15 }; 16 Singleton* Singleton::instance; // 注意,类里面的instance只是声明,static变量必须类外定义 17 18 int main(){ 19 Singleton *instance = Singleton::getInstance(); 20 system("pause"); 21 return 0; 22 }
源码实现解读
上面的代码是完整的控制台程序,VS2010 Pro下可以正确编译运行。下面对这段程序的重点进行介绍一下:
■ 类Singleton拥有一个静态成员指针变量,该指针为整个类共享
■ 类Singleton拥有一个私有的无参构造函数,这说明,在类外是无法通过构造方法来实例化对象的
■ 类Singleton拥有一个getInstance的方法,这个方法内部用于实例化instance成员指针变量
上面的单例实现又被称为懒汉式的单例模式,为什么呢?因为类Singleton的成员instance只有在被调用的时候才会被初始化,所以被称为懒汉式单例实现。
问题缺点
上面的实现有没有问题呢?有。在多线程场景中,上面的代码很容易产生多个实例,为什么呢?我画了一张图,如下:
从上图可以看出,在多线程环境下,如果还是按照上面的代码实现的话,就会发生多次创建instance的情况,所以上面的代码还是有问题的,不过仅仅是在多线程的环境下。
3. 单例模式实现(二)——同步单例模式、饿汉式单例
针对2中出现的多线程问题,我们用下面的代码就可以很轻易的结局。
1 #include <iostream> 2 using namespace std; 3 4 class Singleton{ 5 public: 6 static Singleton getInstance(){ 7 return instance; 8 } 9 private: 10 static Singleton instance; 11 Singleton(){}; // 使得该类不能在外部初始化 12 }; 13 Singleton Singleton::instance; // 注意,类里面的instance只是声明,static变量必须类外定义 14 15 int main(){ 16 Singleton instance = Singleton::getInstance(); 17 system("pause"); 18 return 0; 19 }
上面的代码在类创建的时候,实例instance就被创建了,这样就不会发生多线程多次创建实例的情况了。程序解读如下:
■ 类Singleton拥有一个静态实例变量,而不是指针
■ 类Singleton拥有一个私有构造方法,这跟懒汉式是一样的。
上面的实现为什么叫恶汉式单例呢?因为上面的实现在调用Singleton的getInstance之前就应景实例化了一个静态的属于该类的静态成员变量。
问题缺点
上面的实现结局了多线程问题,那还有没有问题呢?有。仔细观察一下,发现有这样一句话:Singleton instance = Singleton::getInstance(); 创建的对象通过Singleton的赋值运算符被赋值给了instance了,这就不好了,虽然对类Singleton中的instance来说,永远只有一个。但是它被复制了,这样就不是单例了,那怎么办呢?办法很简单,就是把类Singleton的拷贝构造函数和赋值操作符变为private就可以了,修改后的代码如下:
1 #include <iostream> 2 using namespace std; 3 4 class Singleton{ 5 public: 6 static Singleton getInstance(){ 7 return instance; 8 } 9 private: 10 static Singleton instance; 11 Singleton(){}; // 使得该类不能在外部初始化 12 Singleton(const Singleton& singleton); 13 Singleton & operator = (const Singleton&); 14 }; 15 Singleton Singleton::instance; // 注意,类里面的instance只是声明,static变量必须类外定义 16 17 int main(){ 18 //Singleton instance = Singleton::getInstance(); // 无法通过编译 19 system("pause"); 20 return 0; 21 }
4. 单例模式中对象的销毁
对于懒汉式的单例实现,我们在getInstance函数里看到了new字样,那是肯定需要用delete销毁的,否则会出现内存溢出的,那要怎样用代码来实现呢?追加一个析构函数就可以了,参考如下代码:
1 class Singleton{ 2 public: 3 static Singleton* getInstance(){ 4 if(instance == NULL){ 5 instance = new Singleton(); 6 } 7 return instance; 8 } 9 ~Singleton(){ 10 if(instance != NULL){ 11 delete instance; 12 } 13 } 14 private: 15 static Singleton* instance; 16 Singleton(){}; // 使得该类不能在外部初始化 17 }; 18 Singleton* Singleton::instance; // 注意,类里面的instance只是声明,static变量必须类外定义
其他
在java中,解决多线程的问题可以有很多种,本文就不讨论了,有兴趣的可以参考:http://blog.csdn.net/it_man/article/details/5787567,这篇博客写的代码还是挺翔实的。
总结
在不考虑多线程的情况下,可以使用懒汉式单例实现方法。而在有多线程的环境下,最好用饿汉式单例实现方法。同时要注意的是,要把构造函数、默认构造函数、赋值运算符重载都改为private的。
PS:有错误的地方还请指出。谢谢。