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

【更新】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

 

 

 

 

 

 


 

 

你可能感兴趣的:(设计模式,数据库,sqlite,cgi,database,qt)