设计模式(2)-单例模式(Singleton)

来源:互联网 发布:国际船舶网络 编辑:程序博客网 时间:2024/05/24 04:42

【更新】2012-6-11,添加原理说明

【描述】单例模式故名思意,就是运行时只允许存在一个实例,用于限制特定对象只被创建一次。常被用于数据库的设计中。

【原理】(2012-6-11)

理解单例模式,关键是理解static关键字。下面简要对static进行分析:

(1) 既然是static的,为什么程序每调用一次,不会重新初始化,使得变量值不断在变?

static是指存储的位置是静态(固定不变)的,即静态存储区。而不是指变量值是固定不变的。static有个重要的特性:如果将不属于外部接口的内容声明为static,可以防止用户使用预定义接口之外的任何方式访问堆栈中的值。

(2) 类中的static数据成员

不像普通数据成员,静态数据成员是独立于该类的任意对象存在的,每个静态数据成员是与该类(不是该类的对象)关联的对象,并不与该类的对象关联。因此,静态函数没有this指针。

(3) static与单例模式

由于static数据成员存储的位置是固定不变的,实质上,利用指针访问同一静态数据成员的不同实例,是访问同一内存地址。也就是说,访问同一静态数据成员的不同实例,实际上,访问的是同一实例(单例)。

main.cpp下述代码示例,s1、s2、s3,s4,s5是不同实例,分别new了不同的内存。但修改s1->test、s4->test,同时修改了s3,s4,s5三个实例中静态test值。实质上,s1->test、s2->test、s3->test、s4->test、s5->test是“同一”实例。(该测试须将Singleton构造函数声明为public,单例模式下,其构造函数应声明为private,之所以这样做,是为了防止调用构造函数构造对象)

//Test2    qDebug()<<s1->test;//s1->test = 1    s2->test++;    qDebug()<<s1->test;//s1->test = 2    qDebug()<<s2->test;//s2->test = 2    Singleton *s4 = new Singleton;    qDebug()<<s4->test;//s4->test = 1    Singleton *s5 = new Singleton;    qDebug()<<s5->test;//s5->test = 1    s1->test++;    s4->test++;    qDebug()<<s1->test;//s1->test = 3    qDebug()<<s2->test;//s2->test = 3    qDebug()<<s3->test;//s3->test = 3    qDebug()<<s4->test;//s4->test = 3    qDebug()<<s5->test;//s5->test = 3

 

【UML图】

图1 单例模式UML

1 Singleton应用了单例模式,定义了一个类型为Singleton的私有静态的_instance,一个类型为int的公有的_test,以及一个类型为QString的_name对象。

2 运用静态方法createInstance创建实例。

3 定义了两个公有方法setName()、getName()。

 

【示例代码】

singleton.h

#ifndef SINGLETON_H#define SINGLETON_H#include <QString>class Singleton{private:    Singleton();private:    static Singleton* _instance;    QString _name;public:    static int test;public:    static Singleton *createInstance();    void setName(QString name);    QString getName();};#endif // SINGLETON_H


singleton.cpp

#include <QDebug>#include "singleton.h"Singleton* Singleton::_instance=NULL;int Singleton::test = 0;Singleton::Singleton(){    qDebug()<<"construct";    test = 1;}Singleton* Singleton::createInstance(){    if(_instance == NULL)    {        _instance = new Singleton;    }    return _instance;}void Singleton::setName(QString name){    _name = name;}QString Singleton::getName(){    return _name;}


main.cpp

#include <QDebug>#include "singleton.h"int main(void){    Singleton *s1 = Singleton::createInstance();    Singleton *s2 = Singleton::createInstance();    if(s1 == s2)    {        qDebug()<<"s1 , s2 are the same instance";    }    s1->setName("zhangsan");    qDebug()<<s1->getName();    qDebug()<<s2->getName();    s2->setName("lisi");    qDebug()<<s1->getName();    qDebug()<<s2->getName();    //Test1    /*    int i;    i = s1->test;    i = Singleton::test;    s1->createInstance();    Singleton *s3 = new Singleton;    i = s3->test;    //Test2    qDebug()<<s1->test;//s1->test = 1    s2->test++;    qDebug()<<s1->test;//s1->test = 2    qDebug()<<s2->test;//s2->test = 2    Singleton *s4 = new Singleton;    qDebug()<<s4->test;//s4->test = 1    Singleton *s5 = new Singleton;    qDebug()<<s5->test;//s5->test = 1    s1->test++;    s4->test++;    qDebug()<<s1->test;//s1->test = 3    qDebug()<<s2->test;//s2->test = 3    qDebug()<<s3->test;//s3->test = 3    qDebug()<<s4->test;//s4->test = 3    qDebug()<<s5->test;//s5->test = 3    */    return 0;}

 

【运行结果】

construct s1 , s2 are the same instance "zhangsan" "zhangsan" "lisi" "lisi" 


s1、s2是同一实例,调用s2->setName修改s2的同时,修改了s1


【实例剖析】

实例1

Qt中对SQL进行相关操作,就是采用单例模式。以sqlite3数据库进行说明。

1 建立连接时,调用类似下述代码:

bool initSql::createConnection(){    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");    db.setDatabaseName(dbName);    if (!db.open())    {        QMessageBox::warning(0, QObject::tr("Database Error"),db.lastError().text());        return false;    }    return true;}

 

2 断开连接时,调用:

void initSql::closeConnection(){    QString name;    {        name = QSqlDatabase::database().connectionName();    }    QSqlDatabase::database().close();    QSqlDatabase::removeDatabase(name);}

 

这样做,好处是,只需要连接数据库一次,得到静态的实例,就可以在工程任何地方,通过该实例,对数据库进行操作,而不必连接第二次。缺陷是,不能同时创建一个以上的实例。

 

其实,单例模式,优点和缺陷都是由于只能创建一个实例引起的。

优点容易理解,讲讲缺点。

运用sqlite3建立了一个数据库,现在编程对数据库进行访问。现在的问题是,有可能会出现这样的情形,在同一时刻,网页cgi程序和Qt编写的程序要同时对数据库访问。cgi没有采用单例模式,而Qt采用单例模式。CGI操作sqlite3请参考CGI如何用C控制sqlite3?一文。

假设,Qt程序已经与数据库建立了连接。此时,实例是静态的,断开连接后,静态对象并没有撤销,而是继续留在内存中,直到程序结束为止。

Qt在断开连接时,一直会占用该数据库。这样的后果是造成cgi程序无法与数据库建立连接。我们想的办法是,先将数据库与Qt断开连接。让cgi程序建立连接,使用完毕,断开连接。再建立Qt与数据库的连接。更加理想的方式是,每执行一次操作像cgi程序一样,执行类似建立连接->操作->断开连接 的过程。理想总是美好的,现实并非如此。

由于,Qt与数据的连接是单例的,只能创建一个实例。在断开连接后,实例并未被撤销。实际上,在退出程序前,都无法再与数据库建立连接了。连接被关闭的效果是,不能再利用该连接对数据库进行任何操作,并且无法再次连接,直到程序结束后重新开始

 

*这段解释,给Qt 数据库编程敲响了一个警钟,在应用了单例模式的情况下,不要视图关闭数据库连接,“让道”给其他程序,然后重新进行连接。

 

实例2

JavaMe 编程连载(9) - 重构之数据永久存储一文中,阐述了模拟一个通用数据库的方法。在文章最后,分析了单例模式的影响。如果程序中要建立多个实例,那么不要采用单例模式,编码实例是static的。

 

【总结】

1 单例模式利用的是static关键字的特性;

2 单例模式为了防止构造函数创建对象,将其声明为private。

 

 【学习参考】

(1) 单例模式(Singleton)

(2) 设计模式之二 --- Singleton 模式

 

【源码下载】 

Qt设计模式1-8测试源码:http://download.csdn.net/detail/tandesir/4984275
 声明:该源码仅供学习交流,勿用于商业目的。

 

转载请标明出处,仅供学习交流,勿用于商业目的

Copyright @ http://blog.csdn.net/tandesir