设计模式(单例模式、工厂模式、建造者模式、代理模式)

设计模式是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案(设计思想、设计经验)


一、六大原则

1、单一职责原则(Single Responsibility Principle)

类的职责应该单一,一个方法只做一件事。

(1)使用建议

两个完全不⼀样的功能不应该放一个类中,一个类中应该是一组相关性很高的函数、数据的封装。


(2)用例

网络聊天:网络络通信 & 聊天,应该分割成为网络通信类 & 聊天类。


2、开闭原则(Open Closed Principle)

对扩展开放,对修改封闭。


(1)使用建议

对软件实体的改动,最好用扩展而非修改的方式。


(2)用例

超时卖货:商品价格 —— 不是修改商品的原来价格,而是新增促销价格。


3、里氏替换原则(Liskov Substitution Principle)

就是只要父类能出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或异常。

继承类时,一定要重写父类中所有的方法,尤其需要注意父类的 protected 方法,子类尽量不要暴露自己的 public 方法供外界调用。


(1)使用建议

子类必须完全实现父类的方法,子类可以有自己的个性。覆盖或实现父类的方法时,输入参数可以被放大,输出可以缩小(为了父类替换成子类时不会出错)


(2)用例

跑步运动员类 —— 会跑步,子类长跑运动员 —— 会跑步且擅长长跑,子类短跑运动员 —— 会跑步且擅长短跑


4、依赖倒置原则(Dependence Inversion Principle)

高层模块不应该依赖低层模块,两者都应该依赖其抽象。不可分割的原子逻辑就是低层模式,原子逻辑组装成的就是高层模块。

模块间依赖通过抽象(接口)发生,具体类之间不直接依赖。


(1)使用建议

  • 每个类都尽量有抽象类,任何类都不应该从具体类派生。
  • 尽量不要重写基类的方法(结合里氏替换原则使用)。

(2)用例

奔驰车司机类 —— 只能开奔驰; 司机类 —— 给什么车,就开什么车; 开车的人:司机 —— 依赖于抽象


5、迪米特法则(Law of Demeter)

又叫 “最少知道法则”。

尽量减少对象之间的交互,从而减小类之间的耦合。一个对象应该对其他对象有最少的了解。对类的低耦合提出了明确的要求:只和直接的朋友交流, 朋友之间也是有距离的。自己的就是自己的(如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就将其放置在本类中)


(1)用例

老师让班长点名 —— 老师给班长一个名单,班长完成点名勾选,返回结果,而不是班长点名,老师勾选。


6、接口隔离原则(Interface Segregation Principle)

客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上


(1)使用建议

接口设计尽量精简单一,但是不要对外暴露没有实际意义的接口。


(2)用例

修改密码,不应该提供修改用户信息接口,而就是单一的最小修改密码接口,更不要暴露数据库操作。


二、单例模式

一个类只能创建一个对象,即单例模式,该设计模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理

  • 全局只有一个实例对象,所以将单例对象放在静态区 / 堆区(保证只创建一次)。
  •  为了防止其他位置创建该对象将构造函数私有
  •  为了防止拷贝使用 delete 修饰拷贝构造和赋值运算符重载函数

1、饿汉模式(以空间换时间)

程序启动时就会创建一个唯一的实例对象。 因为单例对象已经确定, 所以比较适用于多线程环境中, 多线程获取单例对象不需要加锁, 可以有效的避免资源竞争,提高性能 / 响应速度

静态成员变量只能在类域外进行定义初始化,所以在 main 函数之前就将单例对象定义初始化,此时该单例对象创建在静态区上,而且仅有一个,后面就无法再创建。

想要获取该单例对象只能通过静态成员函数 getInstance() 来获取,静态成员函数可以直接访问静态成员变量 _data。

静态对象是在静态区的,它的生命周期是随着整个程序的,它的初始化构造是在程序初始化阶段完成的。

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。


(1)优点

  • 保证全局(整个进程)只有唯一实例对象。
  • 饿汉模式一开始就创建对象,特别简单。

(2)缺点

  • 假设有多个单例对象 A、B、C,要求它们之间有依赖关系,如果依次创建就无法达到,无法保证顺序可能会导致进程启动速度很慢。

为什么会称之为饿汉模式呢?

不管后面是否会用到这个单例对象,在程序一启动且还没有进入 main 函数之前就创建一个唯一的实例对象。这个过程就像一个饿汉一样,一上来就先吃(创建单例对象),所以称之为 “饿汉模式”。


2、懒汉模式

第一次使用要使用单例对象的时候创建实例对象。如果单例对象构造特别耗费时间或者资源(加载插件、加载网络资源等),可以选择懒汉模式,在第一次使用的时候才创建对象。

如果保证在使用单例对象时才进行实例化呢?不是直接实例化对象,而是定义一个对象的指针(静态资源指针),然后再通过访问接口时发现它为空指针,接着再进行 new 对象。但是这样做会出现一个有关线程安全的问题(当多线程时,进行判断为 nullptr,这时还没有调用 new,当前的线程就被切走了。下一个线程来了还是 nullptr 就又进去 new 了一个对象,然后恢复第一个线程的上下文后又 new 了一个对象,第二个 new 的就将第一个的给覆盖了,所以就出现了错误),这里就可以使用互斥锁(因为加锁需要加在一个锁上才有用,所以我们也要将锁设为静态的,然后在类外进行初始化,这样就保证了只实例化出一个对象来)。但是加锁之后又造成了锁冲突,导致串行化进行(当 new 出一个对象之后,在进行调用 getInstance 的时候,还会不停的加锁解锁),效率降低(加锁解锁是有性能消耗的),所以有了 双检测加锁(double check 检测)。但是又涉及到了代码指令顺序的问题,所以得加上一个 v 关键字来修饰。以上需要关注的因素很多,下面不采取这种方式。了解更多可参考:【C++】特殊类设计-CSDN博客

  • 《Effective C++》的作者 Scott Meyers 提出的一种优雅简便的单例模式 Meyers' Singleton in C++。
  • C++11 Static local variables 特性以确保 C++11 起,静态变量将能够在满足 thread-safe 的前提下唯一地被构造和析构。

翻译:

如果多个线程试图同时初始化同一个静态局部变量,则初始化只会发生一次(使用std::call_once可以为任意函数获得类似的行为)。
此功能的通常实现使用双重检查锁定模式的变体,这将已经初始化的局部静态的运行时开销减少到单个非原子布尔比较。


(1)优点

  • 第一次使用实例对象时创建对象。
  • 进程启动无负载。
  • 多个单例实例启动顺序(通过代码顺序)自由控制。

(2)缺点

  • 复杂。
  • 如果不加锁是会出现线程安全的问题。但是加锁是会十分影响性能的,所以引入了双检查。那么既要保证线程安全 + 又要保证效率的问题。(原先做法)

为什么称之为懒汉模式呢?

懒汉模式又叫做延时加载模式。如果单例对象构造十分耗时或者占用很多资源,比如加载插件、初始化网络连接、读取文件等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢,所以这种情况使用懒汉模式(延迟加载)更好。


三、工厂模式

工厂模式是一种创建型设计模式,它提供了一种创建对象的最佳方式。在工厂模式中,创建对象时不会对上层暴露创建逻辑,而是通过使用一个共同结构来指向新创建的对象,以此实现创建 —— 使用的分离。


1、简单工厂模式

简单工厂模式实现由一个工厂对象通过类型决定创建出来指定产品类的实例。

假设有一个工厂能生产出水果,当客户需要产品的时候就明确告知工厂要生产哪类水果,工厂需要接收用户提供的类别信息,当新增产品的时候,工厂内部去添加新产品的生产方式。 

通过参数控制可以生产任何产品。

这个模式的结构和管理产品对象的方式十分简单,但是它的扩展性非常差,当需要新增产品的时候,就需要去修改工厂类新增一个类型的产品创建逻辑,违背了开闭原则。

在继承中要构成多态还有 2 个条件:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

virtual void name() = 0; 是一个纯虚函数的声明,表示这个函数在基类中没有具体实现。基类 Fruit 包含了纯虚函数,它不能被实例化,因此它是一个抽象类。name() 方法在 Fruit 类中没有实现,任何继承自 Fruit 的类都必须提供 name() 的具体实现。 


(1)优点

  • 简单粗暴,易于理解。使用一个工厂生产同一等级结构下的任意产品。

(2)缺点

  • 所有东西生产在一起,产品太多的话会导致代码量庞大。
  • 开闭原则遵循(开放拓展,关闭修改)的不是太好,要新增产品就必须要修改工厂方法。

2、工厂方法模式

在简单工厂模式下新增多个工厂,多个产品,每个产品对应一个工厂。

假设现在有 A、B 两种产品,则开两个工厂,工厂 A 负责生产产品 A,工厂 B 负责生产产品 B,用户只知道产品的工厂名,而不知道具体的产品信息,工厂不需要再接收客户的产品类别,而只负责生产产品。

定义一个创建对象的接口,但是由子类来决定创建哪种对象,使用多个工厂分别生产指定的固定产品。

工厂方法模式每次增加一个产品时,都需要增加一个具体产品类和工厂类,这会使得系统中类的个数成倍增加,在一定程度上增加了系统的耦合度。


(1)优点

  • 减轻了工厂类的负担,将某类产品的生产交给指定的工厂来进行。
  • 开闭原则遵循较好,添加新产品只需要新增产品的工厂即可,不需要修改原先的工厂类。

(2)缺点

  • 对于某种可以形成一组产品族的情况处理较为复杂,需要创建大量的工厂类。

3、抽象工厂模式

工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。此时可以考虑将⼀些相关的产品组成⼀个产品族(位于不同的产品等级结构中,功能相关联的产品组成的家族),由同一个工厂来统一生产,这就是抽象工厂模式的基本思想。

围绕一个超级工厂创建其他工厂。每个生成的工厂按照工厂模式提供对象。


(1)思想

将工厂抽象成两层:抽象工厂 & 具体工厂子类,在工厂子类种生产不同类型的子产品。 ​


抽象工厂模式适用于生产多个工厂系列产品衍生的设计模式,增加新的产品等级结构复杂,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,违背了 “开闭原则”。


四、建造者模式

建造者模式是⼀种创建型设计模式,使用多个简单的对象一步一步构建成一个复杂的对象,能够将一个复杂的对象的构建与它的表示分离,提供一种创建对象的最佳方式。主要用于解决对象的构建过于复杂的问题。

建造者模式主要基于五个核心类实现:

  • 抽象产品类。
  • 具体产品类:⼀个具体的产品对象类。
  • 抽象 Builder 类:创建一个产品对象所需的各个部件的抽象接口。
  • 具体产品的 Builder 类:实现抽象接口,构建和组装各个部件。
  • 指挥者 Director 类:统一组建过程,提供给调用者使用,通过指挥者来构造产品。


五、代理模式

代理模式代理控制对其他对象的访问,也就是代理对象控制对原对象的引用。在某些情况下,一个对象不适合或者不能直接被引用访问,而代理对象可以在客户端和目标对象之间起到中介的作用。代理模式的结构包括:一个是真正你要访问的对象(目标类)、一个是代理对象。目标对象与代理对象实现同一个接口,先访问代理类再通过代理类访问目标对象。

以租房为例,房东将房子租出去,但是要租房子出去,需要发布招租启示,带人看房,负责维修,这些工作中有些操作并非房东能完成,因此房东为了省事,将房子委托给中介进行租赁。


1、静态代理

在编译时就已经确定好了代理类和被代理类的关系。也就是说,在编译时就已经确定了代理类要代理的是哪个被代理类。


2、动态代理

在运行时才动态生成代理类,并将其与被代理类绑定。这就意味着,在运行时才能确定代理类要代理的是哪个被代理类。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言--01基础数据类型

1.整型 概念:表达整数类型的数据语法: int a 123; // 定义了一个专门用来存储整数的变量a a 456 ; 需要注意的地方: int 的本意是 integer,即整数的意思int a 代表在内存中开辟一块小区域,称为 a,用来…

ado.net 操作sqlite

新建控制台项目 安装nuget包Microsoft.Data.Sqlite 数据库名字和链接 string dbName "test.db"; SqliteConnection? connection null; try {//创建链接connection new SqliteConnection($"Data Source{dbName}");//打开链接connection.Open(); } ca…

【Hot100】LeetCode—160. 相交链表

目录 1- 思路思路 2- 实现⭐160. 相交链表——题解思路 3- ACM 实现 原题连接:160. 相交链表 1- 思路 思路 首先想要找到相交点,需要定义连个指针。两个指针一定得是同步的,例如 A 链表 [1,2,3,4,5] ,链表 B 是 [4,5] 1- 指针对…

大公报发表欧科云链署名文章:发行港元稳定币,建Web3.0新生态

欧科云链研究院资深研究员蒋照生近日与香港科技大学副校长兼香港Web3.0协会首席科学顾问汪扬、零壹智库创始人兼CEO柏亮,在大公报发布联合署名文章 ——《Web3.0洞察 / 发行港元稳定币,建Web3.0新生态》,引发市场广泛讨论。 文章就香港稳定币…

鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射

关于中断部分系列篇将用三篇详细说明整个过程. 中断概念篇 中断概念很多,比如中断控制器,中断源,中断向量,中断共享,中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.用海公公打比方说明白中断各个概念…

Spark SQL Catalyst工作流程

我们写的SQL语句,会经过一个优化器 (Catalyst),转化为 RDD,交给集群执行。 而Catalyst在整个Spark 生态中的地位也是至关重要的。 SQL到RDD中间经过了一个Catalyst,它就是Spark SQL的核心,是针对Spark SQL语句执行过程…

JS获取当前设备名称

在JavaScript中,没有直接获取“当前设备名称”的标准方法,因为这通常涉及访问底层系统信息,而JavaScript在浏览器中运行时通常无权访问这些信息。不过,可以通过用户代理字符串(User-Agent string)来间接推断…

C++ //练习 17.2 定义一个tuple,保存一个string、一个vector<string>和一个pair<string, int>。

C Primer&#xff08;第5版&#xff09; 练习 17.2 练习 17.2 定义一个tuple&#xff0c;保存一个string、一个vector和一个pair<string, int>。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /**********************…

探索数据结构:哈希表的分析与实现

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 贝蒂的主页&#xff1a;Betty’s blog 1. 哈希的引入 1.1. 哈希的概念 无论是在顺序结构还是在树形结构中&am…

CKA-Day03:故障排除

1、cgroup v2 containerd config default | grep -i cgroup grep -i cgroup /etc/containerd/config.toml CRI 2、组件 了解Kubernetes组件并能够修复和调查集群&#xff1a;https://kubernetes.io/docs/tasks/debug-application-cluster/debug-cluster 了解高级调度&#xf…

PHP安全开发

安全开发 PHP 基础 增&#xff1a;insert into 表名(列名 1, 列名 2) value(‘列 1 值 1’, ‘列 2 值 2’); 删&#xff1a;delete from 表名 where 列名 ‘条件’; 改&#xff1a;update 表名 set 列名 数据 where 列名 ‘条件’; 查&#xff1a;select * from 表名 wher…

完美解决html2canvas + jsPDF导出pdf分页内容截断问题

代码地址&#xff1a;https://github.com/HFQ12333/export-pdf.git html2canvas jspdf方案是前端实现页面打印的一种常用方案&#xff0c;但是在实践过程中&#xff0c;遇到的最大问题就是分页截断的问题&#xff1a;当页面元素超过一页A4纸的时候&#xff0c;连续的页面就会…

91. 解码方法 -dp4

. - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/decode-ways/description/ 示例 1&#xff1a; 输入&#xff1a;s &…

基于RDMA技术的Mayastor解决方案

1. 方案背景和挑战 1.1. Mayastor简介 OpenEBS是一个广受欢迎的开源云原生存储解决方案&#xff0c;托管于CNCF&#xff08;云原生计算基金会&#xff09;之下&#xff0c;旨在通过扩展Kubernetes的能力&#xff0c;为有状态应用提供灵活的持久性存储。Mayastor是OpenEBS项目…

wo是如何克服编程学习中的挫折感的?

你是如何克服编程学习中的挫折感的&#xff1f; 编程学习之路上&#xff0c;挫折感就像一道道难以逾越的高墙&#xff0c;让许多人望而却步。然而&#xff0c;真正的编程高手都曾在这条路上跌倒过、迷茫过&#xff0c;却最终找到了突破的方法。你是如何在Bug的迷宫中找到出口的…

LVM 使用以及配置

逻辑卷管理 (LVM) 是一种用于 Linux 系统的存储管理工具&#xff0c;比传统的磁盘分区方法更灵活。LVM 通过将物理存储设备组合成逻辑卷&#xff0c;使得磁盘空间的管理更加动态和便捷。它提供了物理层的抽象&#xff0c;让用户可以创建跨越多个物理磁盘或分区的逻辑卷。 LVM …

带你速通C语言——指针(10)

指针是C语言中最强大但也最容易引起困惑的概念之一。它们直接关联内存管理&#xff0c;使得程序员可以高效地操作数据和内存。下面我将尽量以简单明了的方式介绍指针的基本概念。 1.指针基础 指针本质上是存储内存地址的变量&#xff0c;这个地址指向一个值。通过指针&#xf…

零基础5分钟上手亚马逊云科技核心云架构知识 - 权限管理最佳实践

简介&#xff1a; 欢迎来到小李哥全新亚马逊云科技AWS云计算知识学习系列&#xff0c;适用于任何无云计算或者亚马逊云科技技术背景的开发者&#xff0c;通过这篇文章大家零基础5分钟就能完全学会亚马逊云科技一个经典的服务开发架构方案。 我会每天介绍一个基于亚马逊云科技…

宠物空气净化器是智商税吗吗?哪款最好用?

在当今社会&#xff0c;随着生活节奏不断加快&#xff0c;许多人会感到孤独。因此养猫已成为许多家庭的生活方式之一。他们期待着家里有欢声笑语的出现&#xff0c;希望家里一推开门都是有猫咪等着自己&#xff0c;在自己无人诉说心事的时候&#xff0c;猫咪能给自己一份陪伴。…

Linux日常运维-任务计划(crontab)

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 本小章内容就是Linux进阶部分的日常运维部分&#xff0c;掌握这些日常运维技巧或者方法在我们的日常运维过程中会带来很多方…