为方便读者,本文已添加至索引:
在前几篇笔记中,我们有了解了部分对象创建型模式,包括Builder(建造者)、Abstract Factory(抽象工厂)和Factory Method(工厂方法),今天我们要接触到的是另一种对象创建型模式,既简单又重要的:Singleton(单例)模式。
对一些类来说,只有一个实例是很重要的。比如说,一个软件系统中,应该只有一个窗口管理器;通信设备中,每张板卡上唯一的端口管理器;一个数字滤波器只能有一个A/D转换器。此外,这些唯一的实例还有一个特点是易于访问。我们知道,用一个全局变量可以使得一个对象可以容易被访问,但它并不能防止我们实例化多个对象,同时也会污染命名空间,并非最佳选择。那么如何才能保证一个类只有一个实例并且这个实例易于被访问呢?让我们来了解一下Singleton模式吧!
让我们重新回到时の魔导士的故事世界中去。在Builder模式一节中,他曾利用WorldCreator来创造平行世界(如果读者从这里开始觉得有点不知所云,可以利用前文的传送门翻阅一下之前的笔记),但是为了使世界更加丰富多彩,魔导士不得不亲力亲为地去设计并改造每一处被创建的世界(譬如给白雪公主一行提供的美食工厂等),这无疑是很巨大的工作量。“如果能有人肯帮我做这些琐事,那么我可以多花点时间做些更美妙的研究啦。”魔导士突然灵光一闪,他想到了组建一个议会。在每一个平行世界中,组建起一个足够强大且独一无二的议会,并让它担任起改造世界,维持生态系统良性运转的重任。时の魔导士将它命名为WorldMgr(世界管理者):
1 class WorldMgr { 2 public: 3 static WorldMgr* getInstance(); 4 5 //Existing interface goes here 6 protected: 7 WorldMgr(); 8 private: 9 static WorldMgr* _instance; 10 }
以及它的一个简单实现:
1 WorldMgr* WorldMgr::_instance = 0; 2 3 WorldMgr* WorldMgr::getInstance() { 4 if (_instance == 0) { 5 _instance = new WorldMgr(); 6 } 7 return _instance; 8 }
为了能更好地理解Singleton模式,我们必须详细地解释下上面的代码部分。首先我们利用到静态成员_instance来保存唯一的实例,同时采用静态的getInstance操作提供一个外部访问实例的唯一接口,以此实现了一个Singleton类。其次,我们对构造器加以了保护,以防止WorldMgr被意外的实例化,因为意外的实例化可能会导致多个实例。
正是Singleton模式的引入,使得我们的WorldMgr类可以在平行世界的其他活动中被轻松调用(只需WorldMgr::getInstance() 即可返回它唯一的实例)。但这还不足以让时の魔导士彻底松一口气。作为一个议会,WorldMgr统管世界的方方面面,未免有点太至高无上了。所谓权利的完全集中可能会导致彻底的腐败,倘若魔兽世界中只有一头巨龙--死亡之翼,那么它的堕落将导致艾泽拉斯大陆不复存在。于是,时の魔导士需要一些个体来分摊这份权利,以使得他们之间相互制约、相互平衡。他顺势创造了Titan(泰坦),世界管理者议会的继承类:
掌管生命和死亡的 Iapetus
1 class Iapetus : public WorldMgr { 2 protected: 3 Iapetus(); 4 }
掌管海洋的 Oceanus
1 class Oceanus : public WorldMgr { 2 protected: 3 Oceanus(); 4 }
掌管天空的 Cronus
1 class Cronus : public WorldMgr { 2 protected: 3 Cronus(); 4 }
掌管记忆的 Mnemosyne
1 class Mnemosyne : public WorldMgr { 2 protected: 3 Mnemosyne(); 4 }
等等等等……
既然有这么多子类,如何在getInstance操作中去选择不同的泰坦们呢?这里,我们可以用环境变量的方法来选择。我们假定一个环境变量指定了Titan的名字:
1 WorldMgr* WorldMgr::getInstance() { 2 if (_instance == 0) { 3 const char* name = getenv("TITAN"); 4 5 if (strcmp(name, "Iapetus")) { 6 _instance = new Iapetus(); 7 } 8 else if (strcmp(name, "Oceanus")) { 9 _instance = new Oceanus(); 10 } 11 else if (strcmp(name, "Cronus")) { 12 _instance = new Cronus(); 13 } 14 else if (strcmp(name, "Mnemosyne")) { 15 _instance = new Mnemosyne(); 16 } 17 // ... other Titans 18 else { 19 _instance = new WorldMgr(); 20 } 21 } 22 return _instance; 23 }
不过这个方法的问题就在于,无论何时要定义一个新的Titan(即WorldMgr的子类),getInstance函数都必须被修改。于是我们可以采用另一种注册表的方法来动态链接。让我们看看改变后的WorldMgr:
1 class WorldMgr { 2 public: 3 static void Register(const char* name, WorldMgr*); 4 static WorldMgr* getInstance(); 5 6 //Existing interface goes here 7 protected: 8 WorldMgr(); 9 static WorldMgr* Lookup(const char* name); 10 private: 11 static WorldMgr* _instance; 12 static List<NamePair>* _registry; 13 }
注意我们高亮了新添加的一些语句。Register以给定的泰坦名字注册WorldMgr实例。为保证注册表简单,我们将让它存储一列NamePair对象。每个NamePair将一个名字映射到一个单例。Lookup操作根据给定单件的名字进行查找。我们假定一个环境变量指定了所需要的单件的名字:
1 WorldMgr* WorldMgr::_instance = 0; 2 3 WorldMgr* WorldMgr::getInstance() { 4 if (_instance == 0) { 5 const char* name = getenv("TITAN"); 6 // Lookup returns 0 if there's no such WorldMgr; 7 _instance = Lookup(name); 8 } 9 return _instance; 10 }
那么又在何处让Titan注册自己呢?一种可能是在构造器中,让我们以Mnemosyne为例:
1 Mnemosyne::Mnemosyne() { 2 // ... 3 WorldMgr::Register("Mnemosyne", this); 4 }
当然,除非实例化类否则这个构造器不会被调用。我们可以在包含Mnemosyne实现的文件中定义:
static Mnemosyne theWorldMgr;
如此一来WorldMgr类不再负责创建单例。它的主要职责是使得供选择的单例对象在系统中可以被访问。但是这个静态对象方法还是有一个潜在的缺点:也就是所有可能的WorldMgr子类的实例都必须被创建,否则它们不会被注册。 不过对于时の魔导士而言,这点就不重要啦,因为他确实需要创建所有的Titan们,并让他们开始担负起维护世界运作的重任。
跟随魔导士的步伐,我们见证了一个伟大议会WorldMgr的诞生,同时它也是Singleton模式的很好实例。我们可以看到,Singleton有很多优点:
今天的笔记就到这里了,欢迎大家批评指正!如果觉得可以的话,好文推荐一下,我会非常感谢的!