学习设计模式——单例模式

来源:互联网 发布:陈小春应采儿 知乎 编辑:程序博客网 时间:2024/05/21 08:54
单例模式( singleton pattern ) ——顾名思义,就是只有一个实例;

要点:
1、某个类只有一个实例;
2、它必须自行创建这个实例;
3、向整个系统提供获取这个实例的接口。

用途:工程中很多类需要保证只有一个实例,比如某个软件中用户可以登录使用,我们用User类维护用户相关的数据和操作。这个时候就有很多的问题需要处理:如果登录时候再创建一个User的实例,如何保证其他的地方取到的对象都是登录时候创建的这个对象,如何保证没有多个User对象(假设我们的软件只允许一个用户登录,大多数的软件都这样吧~)这个时候就提现了设计模式的效果——如果不用它,你就会遇见很多麻烦。

实现:
根据上述的要点一一实现
1、如何保证某个类只有一个实例?
实例化一个类的对象必须调用构造函数,如果控制构造函数的权限,就能很好的控制在类的外部创建新的对象。所以这个要点需要我们私有化构造函数。
2、如何让这个类自行创建实例?
解决了1中的问题,我们关闭了在类外部创建实例的可能性,所以我们必须在类的内部创建这个实例,这个也是要点2所要求的。实现这个要点我们可以增加一个类型为本身类的成员变量,或者其他一些办法,待会回头再看。
3、如何提供获取这个实例的接口?
这个要点要求提供一个公有的接口,返回实例即可。

根据以上的分析,大致写出一个初步的单例,慢慢修改。

01 class Singleton
02 {
03   private:
04       Singleton(){}
05   private:
06       Singleton m_Instance;
07   public:
08       Singleton getInstance()
09     {
10       return m_Instance;
11     }
12 };

简单的分析写出了这么简单的代码,但是很显然这是不能通过编译的~~
私有化的构造函数还是正常的,但是下面的代码很有意思,一个类包含了它本身类型的成员变量,如果这样合理的话那我们就可以m_Instance.m_Instance.m_Instance...无限的点下去。这显然是不合理的,而且编译器无法去生成该类的实例,因为会无限的调用构造函数。当然包含本身类型的指针和引用是ok的,包含一个指向自身类型对象的指针确实再正常不过,就像你有你朋友的电话号码一样。

所以我们开始着手去改进,不包含对象了,指针可以吧,试试看呗:

01 class Singleton
02 {
03   private:
04       Singleton(){}
05   private:
06       Singleton *m_pInstance;                //contain pointer is ok
07   public:
08       Singleton* getInstance()
09     {
10       if(NULL == m_pInstance)
11             m_pInstance=new Singleton();//the only instance created here
12       return m_pInstance;
13     }
14 };

好吧,经过我们“慎重”的改进,它已经进步很多了,起码可以通过编译了,这也算是进步吧~
但是如果我们想用这个类做点事情的话,麻烦就来了——你提供了一个公有的接口给我用,我用什么东西来用你的接口呢?

xxx. getInstance();//xxx是什么呢,能调用这个方法的就是Singleton的对象吧,啥? 哪来的对象呢?
刚刚创建了一个完全没有办法用的类,在类外没办法实例,就没办法调用接口,然后就没办法获得我们唯一的那个实例,然后就没有了。

接着改进吧~只有对象的实例才能调用方法么?显然不是——static方法,好吧,恍然大悟,我们一直在强调类只有一个实例,而static不就是实现这个最好的办法么,为整个类所有,而不属于单个的实例。

把getInstance()改成静态的,等等,这次真的得慎重了,想清楚了再“改进”。这样简单的修改又不符合要求,我们在方法里操作了成员变量,静态方法里可以操作非静态的成员么?如果可以的话,那属于整个类的方法调用的时候是操作的哪个对象的成员变量呢?奥,这样是不行的,静态方法只能操作静态成员。

那怎么办?把m_pInstance也设置成静态的合理么?该对象本身就是需要被整个类所有,设置成静态的很合理。
so,继续改进~

01 class Singleton
02 {
03   private:
04       Singleton(){}
05   private:
06       static Singleton *m_pInstance;                
07   public:
08       static Singleton* getInstance()
09     {
10       if(NULL == m_pInstance)
11             m_pInstance=new Singleton();
12       return m_pInstance;
13     }
14 };

终于完成一个看起来和用起来都不错的版本。用的时候注意静态成员的初始化。

问题总是一个接着一个,自从改用指针后我们就需用new一个对象出来,忽然想起课本上的一句话——new和delete必须配对;这里的配对可以从两方面理解:1、用new的就用delete,用的new int[],就需要用delete[];2、new出来的对象就需要用delete去清理它,要不然就泄露了。

那么这我们用到了new,哪去delete呢?我们的only instance是需要在整个程序运行过程中都存在的,它的析构函数会被调用么?显然不去delete它,就不会调用析构函数,如果在析构函数中有一些必须的操作的话(比如关闭文件,释放外部资源等),要怎么做呢~

可以在程序结束之前,delete Singleton::getInstance();这样显得有些丑陋,还是差强人意的实现了调用析构的目的。但是还是不建议这样用,很容易忽略这个代码的加入,而更严重的是,如何保证是在程序结束前做这个事情,万一删了还要用就可能出问题。

我们知道静态成员变量和全局变量一样,到程序结束的时候才会被析构,当然这个析构不需要我们手动去操作。这里的我们用到的成员变量是一个指针,指针占用的内存会被析构,但是指针指向的才是对象,对象不会自动析构。可以增加一个静态成员,它析构的时候就是程序结束的时候,在它的析构函数里,就可以完成之前操作。

01 class Singleton
02 {
03   private:
04       Singleton(){}
05   private:
06       static Singleton *m_pInstance;
07   
08   public:
09       static Singleton* getInstance()
10     {
11           if(NULL == m_pInstance)
12               m_pInstance=new Singleton();
13           return m_pInstance;
14     }
15   
16   class Garbo
17   {
18     public:
19     ~Garbo()
20     {
21       if(Singleton::m_pInstance)
22         delete Singleton::m_pInstance;
23         
24     }
25   };//class Garbo
26   
27   static Garbo m_garbo;
28 };

这样就会调用Singleton的析构函数了(上面代码没有写析构函数),可以自己测试一下~用一个专门的嵌套类,和它的一个静态对象来专门处理对象的析构问题,似乎显得有些复杂。

利用静态成员变量在程序结束的时候析构的特性,我们本身的m_pInstance也是一个静态的成员变量,只是它是个指针,如果是对象本身,就能调用析构函数了。所以我们把指针换成对象,试试看~

01 class Singleton
02 {
03   private:
04       Singleton(){}
05 
06   private:
07       static Singleton m_Instance;
08   
09   public:
10       static Singleton& getInstance()
11     {
12           return m_Instance;
13     }
14 };

产生了一个对象,接口返回这个对象的引用。外面不能调用构造函数,不能产生新对象,等等。。

Singleton single=Singleton::getInstance();
Singleton single(Singleton::getInstance());

我们又忽略了——复制构造函数。既然你提供了获取这个实例的接口,我就可以复制一个,绕开了构造函数。编译器默默的生成了复制构造函数,如果不想使用,需要明确的拒绝。(《effevtive c++》条款06)拒绝的方法就是copy构造函数私有化,声明但不实现。

上面提到提供了获取对象引用的接口,导致可以复制对象,把引用换成接口会如何呢~其实结果是一样的,获取了指针也还是能获取对象。所以都是需要禁掉copy构造函数。改掉之后是:

class Singleton
{
 private:
     Singleton(){}
     Singleton(const Singleton &){}
 
 public:
     static Singleton* getInstance()
   {
       static Singleton m_Instance;
         return &m_Instance;
   }
};

ok,我们完成了最终的版本,完成了单例的三个要素,而且如果需要析构函数,加上就可以。而且把静态成员变量换到了局部的静态变量,代码更加简洁。这时候想起了《c++ primer》里面好像说过,单例的精髓在于局部静态变量的使用,很有道理。

很简单常用的模式,其实有很多为什么,考虑不周再慢慢——改~~