一、介绍

单例模式是保证一个类仅有一个实例,并提供一个访问它的全局访问点。

在下面的情况下可以使用Singleton模式:

1.当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时;

2.当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

二、单例模式的优点和缺点:

优点:

1.对唯一实例的受控访问:

因为Singleton类封装它的唯一实例,所以它可以严格的控制客户怎样以及何时访问它。

2.缩小名空间:

Singleton模式是对全局变量的一种改进。它避免了那些存储唯一实例的全局变量污染名空间。

3.允许对操作和表示的精化:

Singleton类可以有子类,而且用这个扩展类的实例来配置一个应用是很容易的。你可以用你所需要的类的实例在运行时刻配置应用。

4.允许可变数目的实例:

这个模式使得你易于改变你的想法,并允许Singleton类的多个实例。此外,你可以用相同的方法来控制应用所使用的的实例的数目。只有允许访问Singleton实例的操作需要改变。

5.比类操作更灵活:

另一种封装单件功能的方式是使用类操作(即C++中的静态成员函数或者是Smalltalk中的类方法)。但这两种语言技术都难以改变设计以允许一个类有多个实例。此外,C++中的静态成员函数不是虚函数,因此子类不能多态的重定义它们。

缺点:

1.测试:

单例模式使得unit test非常困难。因为它将全局状态引入到了应用中,所以你很难完全隔离其他类对它的依赖。当你想要测试一个依赖单例的类,你不可避免地也需要测试它所依赖的单例。当编写unit test时,我们希望被测试的类能够尽可能的和其他类松耦合,并且这个类的所有依赖项应该能够完全由外部提供(要么通过构造函数传入,要么通过set函数传入),这样被测试的类就能被容易地mock。不幸的是,一旦和单例有依赖关系,那么就会非常不易实现。因为单例引入了紧耦合关系,并且其他类需要自己去获取单例对象。更糟糕的是,单例的全局状态会在test case之间保存,这将带来严重的问题:

a.test case的顺序变得重要;

b.test会出现一些由单例引起的副作用;

c.不能并行的运行多个tests;

d.对同一个test case的多次运行可能会导致不同的结果。

2.隐藏依赖:

通常,当一个类依赖外部其他类时,从它的构造函数或成员函数的形参可以很明显地判断依赖哪些类。因此,当测试的时候,你可以很容易地提供mock类。如果一个类调用单例,那么从它的构造函数或成员函数并不容易看出这点。如果这个类依赖的单例要求使用类似init()这样的方法并提供初始状态来初始化,那么情况将更将糟糕。在这种情况下,使用这个类的用户将无从知道他们应该初始化这个类依赖的单例。如果有一连串级联依赖的单例,那么情况就更加混乱,因为这些级联依赖的单例需要按序初始化。

3.单一责任原则(Single Responsibility Principle):

根据单一责任原则,每个类应该只有一个责任,并只有一个理由去改变。很明显,单例模式违背了这一原则。与分离责任相反,单例类有两个清楚的责任:一是确保只有一个实例被创建;二是这个类的核心功能--访问DB、管理一些独有的资源以及其他任务。

有太多责任和太多理由改变的类很难去维护,因为一种责任中的发生的改变也会影响其他责任中的改变。将类的核心功能与管理类实例的个数这两者耦合在一起极大地减弱了类的可重复使用性。问题是决定一个类是否是单例通常并不取决于这个类本身,而是取决于系统和这个类被使用的上下文。也就是说,同样一个类,依据它被使用的系统,它可以是单例也可以有多个实例。理想情况下,管理类实例的个数的责任应该从这个类本身抽离出来,并由外部管理。典型的例子是依赖注入容器(Dependency Injection container),它管理控件的生命周期(选择具体哪些依赖项来实例化)。这样,同样的类能够以不同的生命周期管理在多个上下文中使用(单个实例,实例个数不受限制等)。

三、单例模式的实现:

#include <iostream>
#include <vector>
#include <mutex>
#include <thread>using namespace std;class Number
{
public:static Number* instance();static void destroy();void setValue(int in){value = in;}int getValue(){return value;}
protected:int value;Number(){cout << ">>> Number constructor >>>" << endl;}~Number(){cout << "<<< Number destructor <<<" << endl;}Number(const Number&) {};Number& operator=(const Number&) {};private:static Number *inst;static std::mutex m_mutex;
};Number* Number::inst = nullptr;
std::mutex Number::m_mutex;Number* Number::instance()
{if (!inst){std::unique_lock<std::mutex> lock(m_mutex);if (!inst){inst = new Number();}}return inst;
}void Number::destroy()
{if (inst){std::unique_lock<std::mutex> lock(m_mutex);if (inst){delete inst;}}
}int main()
{std::vector<std::thread> vt(5);for (int i = 0; i < 5; i++){vt[i] = std::thread([i]() {Number::instance()->setValue(i * 2);cout << "Thread " << std::this_thread::get_id() << ", value: " << Number::instance()->getValue() << endl;});}for (auto& t : vt){t.join();}Number::destroy();
}

这也是常说的懒汉式双重检测法,保证线程安全。优点是资源利用率高,不执行instance()就不会创建实例;缺点是第一次加载时不够快。

其他实现方法:

1>饿汉式:

class Number
{
public:static Number* instance();void setValue(int in){value = in;}int getValue(){return value;}
protected:int value;Number(){cout << ">>> Number constructor >>>" << endl;}~Number(){cout << "<<< Number destructor <<<" << endl;}Number(const Number&) {};Number& operator=(const Number&) {};private:static Number inst;
};Number Number::inst;Number* Number::instance()
{return &inst;
}

这种方式优点是实例在程序执行之前就已经分配内存创建好,调用时速度快,且线程安全。缺点是如果instance()没有调用到,那么创建实例就会造成资源浪费。

2>静态局部对象:

class Number
{
public:static Number* instance();void setValue(int in){value = in;}int getValue(){return value;}
protected:int value;Number(){cout << ">>> Number constructor >>>" << endl;}~Number(){cout << "<<< Number destructor <<<" << endl;}Number(const Number&) {};Number& operator=(const Number&) {};
};Number* Number::instance()
{static Number inst;return &inst;
}

实例在第一次调用时创建生成,在程序运行结束时,自动释放。C++11之后线程安全,C++11标准规定了局部静态变量初始化需要保证线程安全。

四、单例的继承:

class Number
{
public:static Number* instance();static void destroy();static void setType(string t){type = t;delete inst;inst = nullptr;}virtual void setValue(int in){value = in;}virtual int getValue(){return value;}
protected:int value;Number(){cout << "base constructor" << endl;}virtual ~Number(){cout << "base destructor" << endl;}
private:static string type;static Number* inst;static std::mutex m_mutex;
};string Number::type = "decimal";
Number* Number::inst = nullptr;
std::mutex Number::m_mutex;class Octal : public Number
{
public:friend class Number;void setValue(int in){char buf[10];sprintf(buf, "%o", in);sscanf(buf, "%d", &value);}
protected:Octal() {cout << "Octal constructor" << endl;}~Octal(){cout << "Octal destructor" << endl;}
};class Hex : public Number
{
public:friend class Number;void setValue(int in){char buf[10];sprintf(buf, "%x", in);sscanf(buf, "%d", &value);}
protected:Hex(){cout << "Hex constructor" << endl;}~Hex(){cout << "Hex destructor" << endl;}
};Number* Number::instance()
{if (!inst){std::unique_lock<std::mutex> lock(m_mutex);if (!inst){if (type == "octal")inst = new Octal();else if (type == "hex")inst = new Hex();elseinst = new Number();}}return inst;
}void Number::destroy()
{if (inst){std::unique_lock<std::mutex> lock(m_mutex);if (inst){delete inst;}}
}int main()
{Number::instance()->setValue(100);cout << "value is " << Number::instance()->getValue() << endl;Number::setType("octal");Number::instance()->setValue(100);cout << "value is " << Number::instance()->getValue() << endl;Number::setType("hex");Number::instance()->setValue(100);cout << "value is " << Number::instance()->getValue() << endl;Number::destroy();
}

为了能够访问子类的构造函数,父类必须声明为子类的友元类。在《设计模式-可复用面向对象软件的基础》中还提供了一种单例注册表(registry of singleton)的方法。可能的Singleton类的集合不是由Instance定义的,Singleton类可以根据名字在一个众所周知的注册表中注册它们的单例实例。这个注册表在字符串名字和单例之间建立映射。当Instance需要一个单例时,它参考注册表,根据名字请求单例。

参考资料:

单例模式介绍和优点:

1.《设计模式-可复用面向对象软件的基础》

2.https://sourcemaking.com/design_patterns/singleton

单例模式的缺点:https://www.vojtechruzicka.com/singleton-pattern-pitfalls/

单例模式的实现:

1.https://blog.csdn.net/baidu_20876831/article/details/78948180

2.https://blog.csdn.net/zztan/article/details/54691809

单例的继承:

1.《设计模式-可复用面向对象软件的基础》

2.https://blog.csdn.net/fireroll/article/details/79317794

3.https://www.cnblogs.com/faith0217/articles/4083484.html

4.https://sourcemaking.com/design_patterns/singleton/cpp/2

单例的释放:

1.https://sourcemaking.com/design_patterns/to_kill_a_singleton

2.https://blog.csdn.net/fireroll/article/details/79317794

[设计模式] - Singleton单例模式相关推荐

  1. socket可以写成单例嘛_精读《设计模式 - Singleton 单例模式》

    Singleton(单例模式) Singleton(单例模式)属于创建型模式,提供一种对象获取方式,保证在一定范围内是唯一的. 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 其实单例模 ...

  2. socket可以写成单例嘛_精读设计模式 Singleton 单例模式

    Singleton(单例模式) Singleton(单例模式)属于创建型模式,提供一种对象获取方式,保证在一定范围内是唯一的. 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 其实单例模 ...

  3. php实现单例模式类singletonv,php设计模式 singleton (单例模式)

    25种php设计模式,你全都知道吗?下面用代码介绍单例模式(singleton模式)<?php /** * 单例模式 * * 保证一个类仅有一个实例,并提供一个访问它的全局访问点 * */ cl ...

  4. 设计模式之单例模式(Singleton)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式包括:1.FactoryMethod(工厂方法模式):2.Abstract Factory(抽象工厂模式):3.Sin ...

  5. 设计模式之单例模式——Singleton

                        设计模式之单例模式--Singleton 设计意图: 保证类仅有一个实例,并且可以供应用程序全局使用.为了保证这一点,就需要这个类自己创建自己的对象,并且对外有 ...

  6. Java设计模式之单例模式(Singleton Pattern)

    **单例模式:用来创造独一无二的,只能有一个实例的对象设计模式.单例模式确保一个类只有一个实例,并提供一个全局访问点.**相比于全局变量(对对象的静态引用),单例模式可以延迟实例化,而且全局变量不能保 ...

  7. 八.创建型设计模式——Singleton Pattern(单例模式)

    定义 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类,它提供全局访问的方法.单例模式的要点有三个:一是某个类只能有一个实例:二是它必须自行创建这个实例:三是 ...

  8. C#设计模式(1)——单例模式

    原文地址:http://www.cnblogs.com/zhili/p/SingletonPatterm.html 一.引言 最近在设计模式的一些内容,主要的参考书籍是<Head First 设 ...

  9. java设计模式之单例模式(七种方法)

    单例模式:个人认为这个是最简单的一种设计模式,而且也是在我们开发中最常用的一个设计模式. 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个 ...

最新文章

  1. pandas判断日期是否是闰年(is_leap_year)实战: 数据列转化为日期(时间)格式、判断pandas的日期数据列是否是闰年
  2. Python进阶09 动态类型
  3. (转)MySQL索引原理及慢查询优化
  4. linux 命令解码空格,Shell 编程:Bash空格的那点事
  5. requestmapping配置页面后_SpringBoot2.0 基础案例(03):配置系统全局异常映射处理
  6. r语言 悲观剪枝_《R语言编程—基于tidyverse》新书信息汇总
  7. 2021年基金什么时候布局?
  8. 编解码器的学习笔记(十):Ogg系列
  9. [转载]梯度下降小结
  10. 二叉树:一入递归深似海,从此offer是路人
  11. TensorFlow应用实战-18-Policy Gradient算法
  12. Only老K说-Java设计模式之原型模式(Prototype)
  13. ssm实战(1)------数据库表结构
  14. hs8346v5联通 说明书_请教hs8546v5更改华为界面正确方式
  15. parent.layer.open打开的页面向上个页面传值
  16. win10蓝屏提示重新启动_电脑蓝屏五大要素,秒判蓝屏问题及处理!
  17. python实现整数从低位到高位输出与从高位到低位输出
  18. 往数据库里面插入data数据
  19. 【习题35】交互程序三 + 汉化版
  20. BZOJ 4892: [Tjoi2017]DNA(SA+RMQ / SAM)

热门文章

  1. 时间序列趋势判断(三)——Mann-Kendall趋势检验
  2. CSDN Markdown编辑设置图片大小
  3. 浅谈密码学中数论基础
  4. python-----学习资料
  5. Numpy 之 where理解
  6. Docker监控方案之cAdvisor
  7. Unity枚举和字符串的相互转换
  8. java源码阅读Object
  9. websocket.js
  10. LintCode 158: Anagram