写在前面

为方便读者,本文已添加至索引

在前几篇笔记中,我们有了解了部分对象创建型模式,包括Builder(建造者)Abstract Factory(抽象工厂)Factory Method(工厂方法),今天我们要接触到的是另一种对象创建型模式,既简单又重要的:Singleton(单例)模式。

对一些类来说,只有一个实例是很重要的。比如说,一个软件系统中,应该只有一个窗口管理器;通信设备中,每张板卡上唯一的端口管理器;一个数字滤波器只能有一个A/D转换器。此外,这些唯一的实例还有一个特点是易于访问。我们知道,用一个全局变量可以使得一个对象可以容易被访问,但它并不能防止我们实例化多个对象,同时也会污染命名空间,并非最佳选择。那么如何才能保证一个类只有一个实例并且这个实例易于被访问呢?让我们来了解一下Singleton模式吧!

要点梳理

  • 目的分类

    • 对象创建型模式
  • 范围准则
    • 对象(该模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性)
  • 主要功能
    • 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 适用情况
    • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
    • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
  • 参与部分
    • Singleton:定义一个Instance操作,允许客户访问它的唯一实例。Instance是一个类操作(即C++中的一个静态成员函数)。
  • 协作过程
    • 客户只能通过Singleton的Instance操作访问一个Singleton的实例

示例分析 - 泰坦,世界管理者

让我们重新回到时の魔导士的故事世界中去。在Builder模式一节中,他曾利用WorldCreator来创造平行世界(如果读者从这里开始觉得有点不知所云,可以利用前文的传送门翻阅一下之前的笔记),但是为了使世界更加丰富多彩,魔导士不得不亲力亲为地去设计并改造每一处被创建的世界(譬如给白雪公主一行提供的美食工厂等),这无疑是很巨大的工作量。“如果能有人肯帮我做这些琐事,那么我可以多花点时间做些更美妙的研究啦。”魔导士突然灵光一闪,他想到了组建一个议会。在每一个平行世界中,组建起一个足够强大且独一无二的议会,并让它担任起改造世界,维持生态系统良性运转的重任。时の魔导士将它命名为WorldMgr(世界管理者):

 class WorldMgr {
public:
static WorldMgr* getInstance(); //Existing interface goes here
protected:
WorldMgr();
private:
static WorldMgr* _instance;
}

以及它的一个简单实现:

 WorldMgr* WorldMgr::_instance = ;

 WorldMgr* WorldMgr::getInstance() {
if (_instance == ) {
_instance = new WorldMgr();
}
return _instance;
}

为了能更好地理解Singleton模式,我们必须详细地解释下上面的代码部分。首先我们利用到静态成员_instance来保存唯一的实例,同时采用静态的getInstance操作提供一个外部访问实例的唯一接口,以此实现了一个Singleton类。其次,我们对构造器加以了保护,以防止WorldMgr被意外的实例化,因为意外的实例化可能会导致多个实例。

正是Singleton模式的引入,使得我们的WorldMgr类可以在平行世界的其他活动中被轻松调用(只需WorldMgr::getInstance() 即可返回它唯一的实例)。但这还不足以让时の魔导士彻底松一口气。作为一个议会,WorldMgr统管世界的方方面面,未免有点太至高无上了。所谓权利的完全集中可能会导致彻底的腐败,倘若魔兽世界中只有一头巨龙--死亡之翼,那么它的堕落将导致艾泽拉斯大陆不复存在。于是,时の魔导士需要一些个体来分摊这份权利,以使得他们之间相互制约、相互平衡。他顺势创造了Titan(泰坦),世界管理者议会的继承类:

掌管生命和死亡的 Iapetus

 class Iapetus : public WorldMgr {
protected:
Iapetus();
}

掌管海洋的 Oceanus

 class Oceanus : public WorldMgr {
protected:
Oceanus();
}

掌管天空的 Cronus

 class Cronus : public WorldMgr {
protected:
Cronus();
}

掌管记忆的 Mnemosyne

 class Mnemosyne : public WorldMgr {
protected:
Mnemosyne();
}

等等等等……

既然有这么多子类,如何在getInstance操作中去选择不同的泰坦们呢?这里,我们可以用环境变量的方法来选择。我们假定一个环境变量指定了Titan的名字:

 WorldMgr* WorldMgr::getInstance() {
if (_instance == ) {
const char* name = getenv("TITAN"); if (strcmp(name, "Iapetus")) {
_instance = new Iapetus();
}
else if (strcmp(name, "Oceanus")) {
_instance = new Oceanus();
}
else if (strcmp(name, "Cronus")) {
_instance = new Cronus();
}
else if (strcmp(name, "Mnemosyne")) {
_instance = new Mnemosyne();
}
// ... other Titans
else {
_instance = new WorldMgr();
}
}
return _instance;
}

不过这个方法的问题就在于,无论何时要定义一个新的Titan(即WorldMgr的子类),getInstance函数都必须被修改。于是我们可以采用另一种注册表的方法来动态链接。让我们看看改变后的WorldMgr:

 class WorldMgr {
public:
static void Register(const char* name, WorldMgr*);
static WorldMgr* getInstance(); //Existing interface goes here
protected:
WorldMgr();
static WorldMgr* Lookup(const char* name);
private:
static WorldMgr* _instance;
static List<NamePair>* _registry;
}

注意我们高亮了新添加的一些语句。Register以给定的泰坦名字注册WorldMgr实例。为保证注册表简单,我们将让它存储一列NamePair对象。每个NamePair将一个名字映射到一个单例。Lookup操作根据给定单件的名字进行查找。我们假定一个环境变量指定了所需要的单件的名字:

 WorldMgr* WorldMgr::_instance = ;

 WorldMgr* WorldMgr::getInstance() {
if (_instance == ) {
const char* name = getenv("TITAN");
// Lookup returns 0 if there's no such WorldMgr;
_instance = Lookup(name);
}
return _instance;
}

那么又在何处让Titan注册自己呢?一种可能是在构造器中,让我们以Mnemosyne为例:

 Mnemosyne::Mnemosyne() {
// ...
WorldMgr::Register("Mnemosyne", this);
}

当然,除非实例化类否则这个构造器不会被调用。我们可以在包含Mnemosyne实现的文件中定义:

static Mnemosyne theWorldMgr;

如此一来WorldMgr类不再负责创建单例。它的主要职责是使得供选择的单例对象在系统中可以被访问。但是这个静态对象方法还是有一个潜在的缺点:也就是所有可能的WorldMgr子类的实例都必须被创建,否则它们不会被注册。 不过对于时の魔导士而言,这点就不重要啦,因为他确实需要创建所有的Titan们,并让他们开始担负起维护世界运作的重任。

特点总结

跟随魔导士的步伐,我们见证了一个伟大议会WorldMgr的诞生,同时它也是Singleton模式的很好实例。我们可以看到,Singleton有很多优点:

  1. 对唯一实例的受控访问。因为Singleton类封装它的唯一实例,所以它可以严格地控制客户怎样以及何时访问它。
  2. 缩小命名空间。Singleton模式是对全局变量的一种改进。它避免了那些存储唯一实例的全局变量污染名空间。
  3. 允许对操作和表示的精化。Singleton类可以有子类,而且用这个扩展类的实例来配置一个应用是很容易的。我们可以用所需要的类的实例在运行时刻配置应用。
  4. 允许可变数目的实例。这个模式使得我们易于改变想法,并允许Singleton类的多个实例。此外,我们可以用相同的方法来控制应用所使用的实例的数目。只有允许访问Singleton实例的操作需要改变。
  5. 比类操作更灵活。另一种封装单件功能的方式是使用类操作(即C++中的静态成员函数)。但它难以改变设计以允许一个类有多个实例。此外,C++中的静态成员函数不是虚函数,因此子类不能多态的重定义它们。

写在最后

今天的笔记就到这里了,欢迎大家批评指正!如果觉得可以的话,好文推荐一下,我会非常感谢的!

[学习笔记]设计模式之Singleton的更多相关文章

  1. [学习笔记]设计模式之Prototype

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 在笔记Builder模式中,我们曾见到了最初用于创建平行世界的函数createWorld,并且它是Mage类的成员函数(毕竟是专属于魔 ...

  2. [学习笔记]设计模式之Facade

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 Facade(外观)模式定义了一个高层接口,它能为子系统中的一组接口提供一个一致的界面,从而使得这一子系统更加容易使用.欢迎回到时の魔 ...

  3. [学习笔记]设计模式之Abstract Factory

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 在上篇笔记Builder设计模式中,时の魔导士祭出了自己的WorldCreator.尽管它因此能创造出一个有山有树有房子的世界,但是白 ...

  4. [学习笔记]设计模式之Builder

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 作为一个新入职的魔导士呢,哦不,是程序员,我以为并没有太多机会去设计项目的软件架构.但是,工作一段时间之后,自己渐渐意识到,哪怕是自己 ...

  5. [学习笔记]设计模式之Adapter

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 Adapter(适配器)模式主要解决接口不匹配的问题.为此,让我们要回到最初Builder模式创建平行世界时,白雪公主和小霍比特人的谜 ...

  6. [学习笔记]设计模式之Bridge

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 “魔镜啊魔镜,谁是这个世界上最美丽的人?”月光中,一个低沉的声音回荡在女王的卧室.“是美丽的白雪公主,她正和小霍比特人们幸福快乐地生活 ...

  7. [学习笔记]设计模式之Command

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 在上篇Chain of Responsibility(职责链)模式笔记中,我们学习了一种行为型设计模式.今天,我们继续这一主题,来学习 ...

  8. [学习笔记]设计模式之Chain of Responsibility

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 最近时间比较紧,所以发文的速度相对较慢了.但是看到园子里有很多朋友对设计模式感兴趣,我感觉很高兴,能够和大家一起学习这些知识. 之前的 ...

  9. [学习笔记]设计模式之Composite

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 在Composite(组合)模式中,用户可以使用多个简单的组件以形成较大的组件,而这些组件还可能进一步组合成更大的.它重要的特性是能够 ...

随机推荐

  1. Python全栈开发-web框架之django

    一:web框架 什么是web框架? Web应用框架(Web application framework)是一种开发框架,用来支持动态网站.网络应用程序及网络服务的开发.这种框架有助于减轻网页开发时共通 ...

  2. linux常用命令搜索

    解压tar - xzvf webcmp.tar.gz /目的目录 压缩tar - czvf webcmp.tar.gz /压缩源文件 发包命令 cd /cap/sc_bossdata_20140516 ...

  3. cocos2d-x笔记2: 编译到安卓的步骤与注意事项

    博客地址: www.cnblogs.com/wolfred7464/ 不得不说,真心复杂,本篇博客总结的基本是最简最直接的步骤了,不用Cygwin和Ant的,当然用也可以... 以下用 %PROJEC ...

  4. iOS -一些常用的方法

    1.获取本地的语言 + (NSString *)getLocalLanguage { NSString *language = [[[NSUserDefaults standardUserDefaul ...

  5. REST内容协商注解

    @Produces注解: 用于定义方法的响应实体的数据类型.可以定义一个或多个,同时可以为每种类型定义质量因素,质量因素取值范围从0--1的小数值,默认为1. 示例: @Path("conn ...

  6. HTML5 push

    http://blog.csdn.net/yo548720570/article/details/8599947 http://www.oschina.net/question/82993_63312 ...

  7. Ubuntu配置apache

    http://blog.csdn.net/ljchlx/article/details/21978431 http://www.2cto.com/os/201110/107283.html

  8. Linux Screen命令使用

    参考URL: http://jingyan.baidu.com/article/295430f128d8ea0c7e005089.html ~~~~~~~~~~~~~~~~~~~~~~~~ 其它的不提 ...

  9. CycleScrollView实现轮播图

    // //  CycleScrollView.h //  PagedScrollView // //  Created by 李洪强 on 16-1-23. //  Copyright (c) 201 ...

  10. 透过表象看本质!?之二——除了最小p乘,还有PCA

    如图1所示,最小p乘法求得是,而真实值到拟合曲线的距离为.那么,对应的是什么样的数据分析呢? 图1 最小p乘法的使用的误差是.真实值到拟合曲线的距离为 假如存在拟合曲线,设直线方程为.真实值到该曲线的 ...