写在前面

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

  • 设计模式

    魔法手札索引

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

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

要点梳理

  • 目的分类

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

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

写在最后

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

转载于:https://www.cnblogs.com/xieziyu/p/3577001.html

[学习笔记]设计模式之Singleton相关推荐

  1. 设计模式学习笔记——单例(Singleton)模式

    设计模式学习笔记--单例(Singleton)模式 @(设计模式)[设计模式, 单例模式, Singleton, 懒汉式, 饿汉式] 设计模式学习笔记单例Singleton模式 基本介绍 单例案例 类 ...

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

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

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

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 Decorator(装饰)模式,可以动态地给一个对象添加一些额外的职能.为了更好地理解这个模式,我们将时间线拉回Bridge模式笔记的 ...

  4. 设计模式C++学习笔记之三(Singleton单例模式)

    单例模式看起来也蛮简单的,就是在系统中只允许产生这个类的一个实例,既然这么简单,就直接贴代码了.更详细的内容及说明可以参考原作者博客:cbf4life.cnblogs.com. 3.1.解释 main ...

  5. [学习笔记]设计模式[6]-{适配器模式外观模式}

    设计原则 最少知识原则:只和你的密友谈话 这个原则的意思是,在系统设计的过程中,不要让太多的类耦合在一起,免得对系统一部分的修改会影响到其他部分.在设计系统之前,应该首先注意对象与对象之间的交互关系, ...

  6. sheng的学习笔记-设计模式-代理模式

    原理图: 代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问.这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介. 至少在以下集中情况下可以用 ...

  7. sheng的学习笔记-设计模式-单例模式

    转载文章:​​​​​​Java单例模式的5种实现方法_明朗晨光的专栏-CSDN博客_单例模式java​​​​​​ 饿汉 类加载的时候就创建了实例 优点:类加载的时候创建一次实例,避免了多线程同步问题 ...

  8. 学习笔记 ---- 设计模式之观察者模式

    转载: http://www.cnblogs.com/shitouer/archive/2011/09/02/2164048.html Head First里边给出的场景是: 客户需要这样一个App: ...

  9. 步步为营 .NET 设计模式学习笔记系列总结

    设计模式我从开篇到23种设计模式的讲解总共花了进两个月的时间,其间有很多读者给我提出了很好的建议,同时也指出了我的不足,对此我表示感谢,正是由于很多读者的支持我才能坚持的写到最后.在此表示我真诚的谢意 ...

最新文章

  1. 设置主机名 centos redhad7
  2. C++,C++编程,Windows编程,MFC
  3. uboot 的 bootcmd 和bootargs参数详解
  4. 通过火影忍者英雄案例 理解数据类型
  5. 道理与例子【人人都是产品经理:9009】
  6. Window下JDK、Tomcat、eclipse安装与配置
  7. android init重启service(进程)
  8. 集成电路封测行业科普
  9. 笔记本电脑频繁自动重启_电脑为何总是自动重启 电脑自动重启原因【解决方法】...
  10. Eureka服务注册中心搭建
  11. http status failed,jsp页面加载完了,但是浏览器任然在转圈,页面无法操作
  12. 东北大学C语言课程题库题解专栏目录
  13. Swift 周报 第十七期
  14. 【编程实践】编程语言之 R 语言
  15. System.out. 输出到指定文件中
  16. 如何通过网络ActiveSync同步
  17. js 字符串转gbk 数组
  18. 浙里办开发票据认证单点登陆、令牌获取用户信息
  19. springboot入门书籍_2019版JavaEE学习路线图(内含大纲+视频+工具+书籍+面试)
  20. MQ消息队列数据丢失问题

热门文章

  1. HTTP协议版本检测
  2. TIB自动化测试快讯 -- 自动化测试空间一周精选(2011-11-6)
  3. Kafka集群部署与测试
  4. mysql 设置全局变量_MySQL数据库之mysql查看全局变量以及设置全局变量的值
  5. 对字符串进行切分的技巧
  6. Boostnote 离线版本(local 版)
  7. python loop语句_Python——循环语句
  8. 调用RemoteApp
  9. 关于封禁USB接口的心得
  10. mysql导入大量数据_mysql快速导入大量数据问题