[设计模式之禅读书笔记]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:有错误的地方还请指出。谢谢。

posted @ 2012-10-27 23:24  邵贤军  阅读(2061)  评论(4编辑  收藏  举报