5. 创建型模式 - 单例模式


亦称: 单件模式、Singleton

意图

单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

单例模式

问题

单例模式同时解决了两个问题, 所以违反了单一职责原则

  1. 保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。

    它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。

    注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。

一个对象的全局访问节点

客户端甚至可能没有意识到它们一直都在使用同一个对象。

  1. 为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。

    和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。

    还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

如今, 单例模式已经变得非常流行, 以至于人们会将只解决上文描述中任意一个问题的东西称为单例

解决方案

所有单例的实现都包含以下两个相同的步骤:

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  • 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

真实世界类比

政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, ​ “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。

单例模式结构

单例模式结构

  1. 单例 (Singleton) 类声明了一个名为 get­Instance获取实例的静态方法来返回其所属类的一个相同实例。

    单例的构造函数必须对客户端 (Client) 代码隐藏。 调用 获取实例方法必须是获取单例对象的唯一方式。

 伪代码

在本例中, 数据库连接类即是一个单例。 该类不提供公有构造函数, 因此获取该对象的唯一方式是调用 获取实例方法。 该方法将缓存首次生成的对象, 并为所有后续调用返回该对象。

// 数据库类会对`getInstance(获取实例)`方法进行定义以让客户端在程序各处
// 都能访问相同的数据库连接实例。
class Database is// 保存单例实例的成员变量必须被声明为静态类型。private static field instance: Database// 单例的构造函数必须永远是私有类型,以防止使用`new`运算符直接调用构// 造方法。private constructor Database() is// 部分初始化代码(例如到数据库服务器的实际连接)。// ……// 用于控制对单例实例的访问权限的静态方法。public static method getInstance() isif (Database.instance == null) thenacquireThreadLock() and then// 确保在该线程等待解锁时,其他线程没有初始化该实例。if (Database.instance == null) thenDatabase.instance = new Database()return Database.instance// 最后,任何单例都必须定义一些可在其实例上执行的业务逻辑。public method query(sql) is// 比如应用的所有数据库查询请求都需要通过该方法进行。因此,你可以// 在这里添加限流或缓冲逻辑。// ……class Application ismethod main() isDatabase foo = Database.getInstance()foo.query("SELECT ……")// ……Database bar = Database.getInstance()bar.query("SELECT ……")// 变量 `bar` 和 `foo` 中将包含同一个对象。

 单例模式适合应用场景

 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。

 单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。

 如果你需要更加严格地控制全局变量, 可以使用单例模式。

 单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。

请注意, 你可以随时调整限制并设定生成单例实例的数量, 只需修改 获取实例方法, 即 getInstance 中的代码即可实现。

 实现方式

  1. 在类中添加一个私有静态成员变量用于保存单例实例。

  2. 声明一个公有静态构建方法用于获取单例实例。

  3. 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。

  4. 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。

  5. 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。

 单例模式优缺点

  •  你可以保证一个类只有一个实例。
  •  你获得了一个指向该实例的全局访问节点。
  •  仅在首次请求单例对象时对其进行初始化。
  •  违反了单一职责原则。 该模式同时解决了两个问题。
  •  单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
  •  该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
  •  单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。

 与其他模式的关系

  • 外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。 单例对象可以是可变的。 享元对象是不可变的。 抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。

 代码示例

/*** The Singleton class defines the `GetInstance` method that serves as an* alternative to constructor and lets clients access the same instance of this* class over and over.*/
class Singleton
{/*** The Singleton's constructor should always be private to prevent direct* construction calls with the `new` operator.*/protected:Singleton(const std::string value): value_(value){}static Singleton* singleton_;std::string value_;public:/*** Singletons should not be cloneable.*/Singleton(Singleton &other) = delete;/*** Singletons should not be assignable.*/void operator=(const Singleton &) = delete;/*** This is the static method that controls the access to the singleton* instance. On the first run, it creates a singleton object and places it* into the static field. On subsequent runs, it returns the client existing* object stored in the static field.*/static Singleton *GetInstance(const std::string& value);/*** Finally, any singleton should define some business logic, which can be* executed on its instance.*/void SomeBusinessLogic(){// ...}std::string value() const{return value_;} 
};Singleton* Singleton::singleton_= nullptr;;/*** Static methods should be defined outside the class.*/
Singleton *Singleton::GetInstance(const std::string& value)
{/*** This is a safer way to create an instance. instance = new Singleton is* dangeruous in case two instance threads wants to access at the same time*/if(singleton_==nullptr){singleton_ = new Singleton(value);}return singleton_;
}void ThreadFoo(){// Following code emulates slow initialization.std::this_thread::sleep_for(std::chrono::milliseconds(1000));Singleton* singleton = Singleton::GetInstance("FOO");std::cout << singleton->value() << "\n";
}void ThreadBar(){// Following code emulates slow initialization.std::this_thread::sleep_for(std::chrono::milliseconds(1000));Singleton* singleton = Singleton::GetInstance("BAR");std::cout << singleton->value() << "\n";
}int main()
{std::cout <<"If you see the same value, then singleton was reused (yay!\n" <<"If you see different values, then 2 singletons were created (booo!!)\n\n" <<"RESULT:\n";   std::thread t1(ThreadFoo);std::thread t2(ThreadBar);t1.join();t2.join();return 0;
}
执行结果
If you see the same value, then singleton was reused (yay!
If you see different values, then 2 singletons were created (booo!!)RESULT:
BAR
FOO

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/294143.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

postman几种常见的请求方式

1、get请求直接拼URL形式 对于http接口&#xff0c;有get和post两种请求方式&#xff0c;当接口说明中未明确post中入参必须是json串时&#xff0c;均可用url方式请求 参数既可以写到URL中&#xff0c;也可写到参数列表中&#xff0c;都一样&#xff0c;请求时候都是拼URL 2&am…

Mysql 数据库APi 编程(c/c++)-1.0

MySQL数据库API库 访问MySQL服务器&#xff0c;这需要使用mysqlclient库&#xff0c;MySQL的大多数客户端API&#xff08;除Java和.NET&#xff09;都是通过这个库来和MySQL服务器通讯的&#xff0c;而这个库正是使用C语言编写的。 可使用mysql -V 命令查看当前系统内所使用的…

【单调栈】LeetCode:2818操作使得分最大

作者推荐 map|动态规划|单调栈|LeetCode975:奇偶跳 涉及知识点 单调栈 题目 给你一个长度为 n 的正整数数组 nums 和一个整数 k 。 一开始&#xff0c;你的分数为 1 。你可以进行以下操作至多 k 次&#xff0c;目标是使你的分数最大&#xff1a; 选择一个之前没有选过的 非…

【数据结构】队列的使用|模拟实现|循环队列|双端队列|面试题

一、 队列(Queue) 1.1 概念 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出FIFO(First In First Out) 入队列&#xff1a;进行插入操作的一端称为队尾&#xff08;Tail/Rear&#xff09; 出队列…

.net core webapi 自定义异常过滤器

1.定义统一返回格式 namespace webapi;/// <summary> /// 统一数据响应格式 /// </summary> public class Results<T> {/// <summary>/// 自定义的响应码&#xff0c;可以和http响应码一致&#xff0c;也可以不一致/// </summary>public int Co…

使用代理服务器和Beautiful Soup爬取亚马逊

概述 Beautiful Soup 是一个用于解析 HTML 和 XML 文档的 Python 库&#xff0c;它能够从网页中提取数据&#xff0c;并提供了一些简单的方法来浏览文档树、搜索特定元素以及修改文档的内容。在本文中&#xff0c;我们将介绍如何使用代理服务器和Beautiful Soup库来爬取亚马逊…

洛谷 NOIP2016 普及组 回文日期

这道题目本来是不难想思路的。。。。。。 然而我第一次做的时候改了蛮久才把代码完全改对&#xff0c;主要感觉还是不够细心&#xff0c;敲的时候也没注意见检查一些小错误&#xff0c;那么接下来不说废话&#xff0c;请看题干&#xff1a; 接下来请看输入输出的样例以及数据范…

点云从入门到精通技术详解100篇-基于激光点云的隧道病害检测

目录 前言 国内外研究现状 国内研究现状 国外研究现状 点云数据处理 2.1 三维激光扫描技术 2.2 点云数据特点及建立索引结构 2.3 点云数据处理 2.3.1 点云拼接 2.3.2 点云去噪 2.3.3 点云分割 2.3.4 点云投影 2.3.5 点云转化灰度图 基于激光点云的隧道裂缝提取 …

java并发编程八 CAS 与 volatile和原子整数与原子引用

文章目录 CAS 与 volatile慢动作分析volatile为什么无锁效率高CAS 的特点 原子整数原子引用不安全实现安全实现-使用锁安全实现-使用 CAS CAS 与 volatile 在java并发编程七六中&#xff0c;可以看到的 AtomicInteger 的解决方法&#xff0c;内部并没有用锁来保护共享变量的线…

Wavesurfer.js绘制波形图

HTML使用Wavesurfer.js 要使用wavesurfer.js&#xff0c;首先需要在HTML文件中引入Wavesurfer.js库&#xff0c;然后创建一个音频元素并将其添加到页面中。接下来&#xff0c;初始化Wavesurfer实例并配置相关选项。以下是一个简单的示例&#xff1a; 在HTML文件中引入Wavesurf…

PHP初学习

PHP 基础知识 <?php #这是一个注释echo "<h1>hello world</h1>"; //字符串拼接$message"hello";$name"你好";$message."".$name;echo "<br>";//单引号与双引号的区别&#xff0c;单引号无法解析变量…

音视频类App广告变现如何破局,最大化广告变现收益,让应用增收?

音视频App已然成为了我们日常获取、发布和交换信息的重要方式&#xff0c;在音视频行业不断的拓展中&#xff0c;用户的渗透率提升。 据数据显示&#xff0c;我国网络视听用户的规模已达9亿人次&#xff0c;网民使用率也突破了90%。庞大的市场规模和用户需求吸引了大批开发者和…