当前位置: 首页 > news >正文

探索设计模式——单例模式详解

        前言:设计模式的作用主要是为了——利用设计方式的重用来自动地提高代码的重新利用、提高代码的灵活性、节省时间, 提高开发效率、低耦合,封装特性显著, 接口预留有利于扩展。

        设计模式的种类有很多种,本篇内容主要讲解其中的单例模式。

什么是单例模式

        单例模式是创建型模式的一种。顾名思义, 单例模式就是全局只有一个实例化对象。该种模式可以保证一个类不能创建新的对象。 也就是不能直接定义和new。      单例模式保证了全局只有唯一一份实例化对象, 而且它能够自行实例化并且向整个系统提供这个实例。这种类,提供了全局的方法, 叫做单例类, 而这种设计方法, 叫做单例模式。

        同时, 单例模式又分为饿汉模式和懒汉模式两种。下面为他们两个的特性:

  • 饿汉模式:程序开始前先将唯一实例创建出来。
  • 懒汉模式:第一次使用时再将唯一实例创建出来。

        两者的优缺点:

  • 饿汉模式:优点是设计简单。但缺点是可能导致程序的启动较慢,不能延迟加载。并且当有多个单例对象的时候, 哪个对象先生成不确定。
  • 懒汉模式:优点是解决了程序启动慢的问题, 延迟了加载。 并且当有多个单例对象的时候, 可以创建的顺序。 但缺点是设计比较复杂, 同时存在线程安全问题, 解决线程安全问题会导致程序的性能降低。

        综上, 我们要知道单例模式的三个主要特性: 

  • 一个类里面只能有一份实例。
  • 这个类能够自行创建这个实例。
  • 必须向整个系统提供这个实例。

为什么要有单例模式:

        在我们的电脑中其实有许多程序我们都只能打开一次, 其实这就是应用了单例模式。 比如当我们打开“设置”, 如果你多次点击“设置”。 他也只能打开一个设置窗口(如果你打开了多个,并且不是你本人自己修改了操作系统。那么请给你的电脑杀一下毒)。 我们不妨想一下, 如果我们能够打开多个设置, 那么是不是就显得有点多余, 因为我们只使用一个设置窗口就可以,不需要使用多个。开启多个设置窗口不仅会占用系统上的资源,并且, 如果我们能够打开多个设置窗口。 那么我们在这个窗口设置成这个样子, 在那个窗口设置成那个样子。 最终, 设置的结果是用哪个窗口的呢?所以, 这都是问题。 而单例模式就是为了解决这些的问题。 

代码实现

      饿汉模式

        饿汉模式就是在程序启动之前就自行创建好了这个实例。 这一点我们可以通过静态成员变量来实现, 同时为了保证类的封装性, 我们将这个静态成员变量私有化:

	class Only_Instance {private: static Only_Instance _inst;        //私有化唯一实例};Only_Instance Only_Instance::_inst;         //静态成员变量的定义。

        然后为了保证全局只有唯一一份实例, 我们需要私有化该类的构造函数, 这样就能保证类外不能随意创建对象。 

	class Only_Instance {private: static Only_Instance _inst;        //类唯一实例//私有化构造函数private: Only_Instance() {};                private: Only_Instance(const Only_Instance&) = delete;       };Only_Instance Only_Instance::_inst;         //静态成员变量定义

         如此一来, 我们就能保证:该类只有唯一一份实例化对象。 并且能够在程序启动之前自行创建一份实例。

        那么就要解决访问这个唯一一份实例的问题, 我们如何对它进行访问? 我们可以通过一个共有的方法来访问它, 并且因为我们没有实例化对象调用成员函数以及方法的全局性, 所以这个方法必须是静态的, 全局的:

	class Only_Instance {private: static Only_Instance _inst;        //类唯一实例//私有化构造函数private: Only_Instance() {};                private: Only_Instance(const Only_Instance&) = delete;       public: static Only_Instance* getInstance()   //公有的全局方法{return &_inst;}};Only_Instance Only_Instance::_inst;         //静态成员变量定义

这样,我们就设计出了一个简单的饿汉模式。但是饿汉的不能延迟加载, 启动慢是一个问题。 为了解决, 这个问题。 我们这里看一下另一个设计模式——懒汉模式

        懒汉模式

懒汉模式是在第一次使用的时候自行生成实例。 能够延迟加载, 解决了启动慢的问题。

懒汉模式同样需要私有化构造函数, 不能在外部创建实例化对象:

	class Only_Instance {//构造函数private:Only_Instance() {};private:Only_Instance(const Only_Instance&) = delete;};

为了能够第一次才实例化对象, 我们需要在类地内部创建对象。 那么就不能定义一个全局的单例对象, 应该定义一个全局的单例对象指针, 并且这个指针也是私有的:

	class Only_Instance {//构造函数private:Only_Instance() {};private:Only_Instance(const Only_Instance&) = delete;private:static Only_Instance* _inst;                       //全局的对象指针。};

 那么如何才能访问这个指针, 我们用到和饿汉相同的方式。 就是定义一个全局的静态方法:

	class Only_Instance {//构造函数private:Only_Instance() {};private:Only_Instance(const Only_Instance&) = delete;private:static Only_Instance* _inst;                       //全局的对象指针。public:static Only_Instance* getInstance()     //访问inst{if (_inst == nullptr) {_inst = new Only_Instance();}return _inst;}};

        这样, 就能创建一个通过受控地_inst, 随时访问我们规定的方法getInstance。

        然后, 为了能够手动的销毁这个实例, 我们还要提供一个自动释放的接口:

		//销毁实例化对象public static void DelInstance() {delete _inst;}

 并且,为了资源能够自动释放。 我们可以定义一个内部类成员。 当这个成员销毁的时候, 那么调用inst的析构, 即:

	class Only_Instance {//构造函数private:Only_Instance() {};private:Only_Instance(const Only_Instance&) = delete;private:static Only_Instance* _inst;                       //全局的对象指针。public:static Only_Instance* getInstance()     //访问inst{if (_inst == nullptr){_inst = new Only_Instance();}return _inst;}//销毁实例化对象static void DelInstance() {delete _inst;}class auto_des{public:~auto_des() {delete _inst;}};static auto_des _ad;};Only_Instance::auto_des Only_Instance::_ad;

        但是, 这个懒汉模式此时有另外的问题。 当涉及多线程时,如果我们一个程序跑到了if语段, 另一个程序也跑到了if语段。 那么如果两个if语段同时执行, 那么就会造成创建多个实例对象的情况。 为了避免这种情况,这里就要用到加锁地操作。 这个由于博主知识板块不太完整, 暂时不进行详细讲解。

        想要了解地请看大佬的这篇文章:确保对象的唯一性——单例模式 (三)_青兮科技公司开发人员使用单例模式实现了负载均衡器的设计,但是在实际使用中出现-CSDN博客

其他简单特殊类设计

只能在堆上创建对象

        只能在堆上创建对象, 只能在堆区创建对象。 那么我们就要让这个类无法随便创建对象。 那么使用的方法就是私有化或者封掉构造函数。 即:

class Only_Heap
{
public:Only_Heap* CreatObj(){return new Only_Heap;}//要注意将拷贝构造禁掉Only_Heap(const Only_Heap& pt) = delete;
private://Only_Heap(){cout << "构造对象" << endl;}int a = 1;
};

只能在栈上创建对象

只能在栈上创建对象和在堆上类似。 都是将构造函数封掉,然后在类里面创建对象返回给外面。

	//只能在栈上创建对象class Only_Stack {public://公有一个在栈区创建对象的函数, 并进行值返回static Only_Stack* CreatObj(){Only_Stack obj;return &obj;}//对于new来说。 一般情况下调用自动生成的new。 但是我们也可以自己实现一个operator new。同时, 如果我们禁掉这个new。 那么编译器也不会自动生成了, 参数是size_t//void* operator new(size_t size) //{//	cout << "operator new" << endl;//	return malloc(size);//}/*	Only_Stack(Only_Stack&) = delete;*/void* operator new (size_t size) = delete;private://首先私有构造函数。防止能够随便创建对象, 但是为了能在栈区创建对象, 还要定义构造Only_Stack() {}};

不能够继承的类

一个类如果不想被其他的类继承, 那么我们可以给这个类加上关键字final:

	class _final_class final {//…………};

不能被拷贝的类

一个类如果不能进行拷贝工作, 那么就要将它的拷贝构造和拷贝赋值都封掉:

	//设计一个类, 不能够进行拷贝class None_Copy {private://将拷贝构造和赋值重载只声明。 不实现。	禁止拷贝, 只需要让这个类无法调用拷贝构造和赋值重载即可。//1、为什么要设置成为私有, 是因为如果只声明, 不设置成私有, 那么如果用户在类外面定义了拷贝构造或者赋值重载, 那么就可以进行拷贝或者赋值操作了//2、为什么要只声明, 一方面是因为设置成私有并不会禁止类内部进行调用拷贝。 不定义就可以防止类的内部进行拷贝。 另一方面是因为定义了也不会用, 不写更简单None_Copy(const None_Copy& cp);None_Copy& operator=(const None_Copy& cp);  };

以上, 就是本节的全部内容。 有关设计模式的知识, 博主现在学习的不多, 以后学习更多知识后会更新。 当下如果想要学习更多设计模式可以看一下这个大佬(下面放链接了)的文章, 他为自己的文章建立了一个索引:

史上最全设计模式导学目录(完整版)_史上最全设计模式lovelion-CSDN博客

LoveLion-CSDN博客

相关文章:

  • matplotlib 创建多个子图
  • TWM论文阅读笔记
  • ChatTTS 推荐及使用说明
  • Spring框架的最新进展:2023年Spring Boot和Spring Cloud功能更新
  • 数据分析的Excel基础操作
  • 节能减排如何替电子行业巨头降低成本
  • [程序员] 表达的能力
  • 在公司的一些笔记
  • 【ARM 安全系列介绍 3.7 -- SM4 对称加密算】
  • Java中的图形用户界面开发
  • 【MySQL进阶之路 | 高级篇】SQL执行过程
  • 用Vite基于Vue3+ts+DataV+ECharts开发数据可视化大屏,即能快速开发又能保证屏幕适配
  • PCB行业迈入数字化新时代,智能工厂引领未来制造
  • 供应链投毒预警 | utilitytool系列Py包开展XenoRAT远控木马投毒
  • Android-Android Studio-FAQ
  • chrome扩展demo1-小时钟
  • CSS 专业技巧
  • git 常用命令
  • HTML中设置input等文本框为不可操作
  • JavaScript设计模式之工厂模式
  • java正则表式的使用
  • swift基础之_对象 实例方法 对象方法。
  • Three.js 再探 - 写一个跳一跳极简版游戏
  • Twitter赢在开放,三年创造奇迹
  • ubuntu 下nginx安装 并支持https协议
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 如何使用 JavaScript 解析 URL
  • 移动端唤起键盘时取消position:fixed定位
  • Nginx实现动静分离
  • # Swust 12th acm 邀请赛# [ E ] 01 String [题解]
  • (4) PIVOT 和 UPIVOT 的使用
  • (c语言+数据结构链表)项目:贪吃蛇
  • (备份) esp32 GPIO
  • (二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (没学懂,待填坑)【动态规划】数位动态规划
  • (三十)Flask之wtforms库【剖析源码上篇】
  • .NET Framework 4.6.2改进了WPF和安全性
  • .NET Micro Framework初体验(二)
  • .NET 命令行参数包含应用程序路径吗?
  • .Net 知识杂记
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)
  • .NET连接数据库方式
  • .NET命令行(CLI)常用命令
  • @Slf4j idea标红Cannot resolve symbol ‘log‘
  • @synthesize和@dynamic分别有什么作用?
  • @Transactional注解下,循环取序列的值,但得到的值都相同的问题
  • [ 代码审计篇 ] 代码审计案例详解(一) SQL注入代码审计案例
  • [ACTF2020 新生赛]Upload 1
  • [Angular 基础] - 指令(directives)
  • [C++提高编程](三):STL初识
  • [CODE:-5504]没有[SYS.SYSOBJECTS]对象的查询权限