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