k_eckel:http://www.mscenter.edu.cn/blog/k_eckel & http://k-eckel.cnblogs.com
前几天因为某些事情,和在ATC工作的一位朋友S聊了些技术方面的问题。S在学校的时候就是很,那种传说中的N人,S 在Review我之前写的Visual CMCS的一些代码,就问到其中一个类为什么要使用Singleton模式?代码是几个月前写的,写的时候正是自己对设计模式有一些学习积累和思考的时候,因此里面用到了不少的设计模式。当时简单的考虑就是因为那是一个工厂类,我在程序生命周期内仅需要一个该工厂类的对象就可以了,因此很直观地使用了Singleton模式。当然我当时的回答就是因为仅需要提供一个对象就可以,S马上就问那为什么不用一个全局变量来实现?我知道,在纯面向对象支持的编程语言(例如C#/Java)中,这个问题可以很简单地理解为没有全局变量这样一个概念支持,但是对于向C++等这种支持面向过程的编程语言中这个回答就不是很合适了(至少像在找借口)。
实际上这个问题的答案并不重要(虽然是这个文档的主题),更加重要的是我们在作设计的时候实际上要好好综合考虑,为什么要使用这种设计模式,也就是要防止滥用设计模式的情况。Singleton模式可能可以说是最简单的设计模式了,其应用的场景和示例实在是没有太多可以再重复,但是以下的两个问题还是需要我们进一步的审视:
1) Singleton模式 VS 全局变量。很多情况下,我们使用Singleton模式达到的效果和
全局变量达到的效果类似。但是,全局变量不能防止实例化多个对象。GoF在《设计模式》中给出了Singleton模式的意图“保证一个类仅有一个对象,并提供一个访问它的全局访问点”,因此全局变量可以达到后面半句的效果,但是却不能保证仅有一个对象被实例化。另外,使用全局变量将使得对象在无论是否用到都要被创建,而Singleton模式则没有这个瑕疵。
2) Singleton的子类化问题。一般来说Singleton的子类并不是Singleton,因此在保证
Singleton的正确子类化,在实现上要注意以下几点(C++实现):
① 父类Singleton的构造函数为Protected,目的是为了要让Singleton子类访问,而
不让Client程序访问(防止被其他方式实例化类);Singleton子类构造函数声明为private或者protected,并且将父类Singleton声明为子类Singleton的友元,目的是在父类Singleton中可以实例化子类Singleton,而Client程序不可访问(防止被其他方式实例化类)。
② 我们必须改写父类Singleton中的Instance方法(获得唯一实例方法)。因为Instance
是static的成眼函数,不能以多态的方式实现之。因此我们必须在父类Singleton中就是提供真正实例化Singleton子类的信息。我们可以通过到某一个专门的地方获取Singleton子类的信息,例如提供一个获取函数,在Instance实例化Singleton子类之前获得这个信息,再根据这个信息去实例化具体的Singleton子类。我这里提供的示例程序中是,提供一个全局的GetSingletionType(),返回应该实例化的Singleton具体子类。具体的实现则是是通过随机数来确定的方式,详细请参看代码。以下就将整个代码给出:
|
//Singleton.h #ifndef _SINGLETON_H_ #define _SINGLETON_H_ #include <iostream> using namespace std; class Singleton { public: static Singleton* Instance(); virtual void PrintInfo(); protected: Singleton(); private: static Singleton* _instance; }; class SingletonDeriveA:public Singleton { private: friend class Singleton; SingletonDeriveA(); public: virtual ~SingletonDeriveA(); void PrintInfo();
}; class SingletonDeriveB:public Singleton { private: friend class Singleton; SingletonDeriveB(); public: virtual ~SingletonDeriveB(); void PrintInfo();
}; char* GetSingletionType(); #endif //~_SINGLETON_H_ |
|
//Singleton.cpp #include "Singleton.h" #include <ctime> //for time #include <iostream> using namespace std; Singleton* Singleton::_instance = 0; Singleton::Singleton() { cout<<"Singleton...."<<endl; } Singleton* Singleton::Instance() { const char* type = GetSingletionType(); if (_instance == 0) { if (strcmp(type,"SingletonDeriveA") == 0) { _instance = new SingletonDeriveA(); } else if (strcmp(type,"SingletonDeriveB") == 0) { _instance = new SingletonDeriveB(); } else { _instance = new Singleton(); } } return _instance; } void Singleton::PrintInfo() { cout<<"Singleton type:Singleton"<<endl; } SingletonDeriveA::SingletonDeriveA() { cout<<"SingletonDeriveA...."<<endl; } SingletonDeriveA::~SingletonDeriveA() { } void SingletonDeriveA::PrintInfo() { cout<<"Singleton type:SingletonDeriveA"<<endl; } SingletonDeriveB::SingletonDeriveB() { cout<<"SingletonDeriveB...."<<endl; } SingletonDeriveB::~SingletonDeriveB() { } void SingletonDeriveB::PrintInfo() { cout<<"Singleton type:SingletonDeriveB"<<endl; } char* GetSingletionType() { //随机返回一个Singleton子类 srand((unsigned int)time(NULL)); int SINGLETON_TYPE = rand() % 3; switch (SINGLETON_TYPE) { case 0: return "SingletonDeriveA"; break; case 1: return "SingletonDeriveB"; break; case 2: return "Singleton"; break; default : return "Singleton"; break; } } |
|
//main.cpp #include "Singleton.h" #include <iostream> using namespace std; int main(int argc,char* argv[]) { for (int i = 0; i < 10; ++i) { Singleton* sgn = Singleton::Instance(); sgn->PrintInfo(); } //Singleton* sgn1 = new SingletonDeriveA(); //compile error,保证不能通过其他方式实例化类 return 0; } |
测试程序运行的结果是,虽然我们请求了10次Singleton对象,但是只实例化了一次(从调用构造函数的次数就可以知道)。比较遗憾也是要说明的是:由于GetSingletionType()实现策略不是很好,运行后10次返回的随机数取模后的结果是一样的,因此获得Singleton对象是一样的,但是不同时刻运行的结果可以不一样。调试发现,rand()返回结果不同,但是赋值后就变得一样了,由于这不是重点,故放弃,哪位有好的建议,请不吝赐教。
浙公网安备 33010602011771号