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

news2024/11/13 11:40:24

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


一、六大原则

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、动态代理

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[嵌入式 C 语言] 知识库

一、数据类型 1.1 基本数据类型 自定义类型基础类型占用字节数取值范围描述mls8char1-128 to 1278 位有符号整数mlu8unsigned char10 to 2558 位无符号整数mlvu8volatile unsigned char10 to 2558 位无符号整数,具有 volatile 属性mls16short2-32768 to 3276716 位…

TreeUtils 树工具类

数据展示: 如图:部门树数据 ,树形的基础数据 id 、 parentId 、label 便可形成 嵌套对象字段如下:{id: 103, parentId: 101, label: "研发部门", weight: 1} 一、工具类 继承了 hutoo 的工具类 TreeUtil &#xff08…

springboot+Quartz通过数据库控制定时任务执行与时间

前言 在我们的springboot项目中,有很多种实现定时任务的方式 有用最简单的 Scheduled 实现定时任务,即: import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;Component EnableScheduling p…

Arthas相关命令

官方网站:命令列表 | arthas 也可以用idea的插件arthas-idea的插件根据你想定位的代码生成命令 jvm 相关 dashboard - 当前系统的实时数据面板getstatic - 查看类的静态属性heapdump - dump java heap, 类似 jmap 命令的 heap dump 功能jvm - 查看当前 JVM 的信息l…

Studying-刷题补充| 数组:58.区间和、44. 开发商购买土地

目录 58.区间和 44. 开发商购买土地 总结 58.区间和 文档讲解:代码随想录58.区间和 题目:58. 区间和(第九期模拟笔试) (kamacoder.com) 学习:本题最直接的做法是,将数组Array保存好后,通过…

linux - mathematica 安装教程

注意事项: 文件目录不能有空格,不能有中文 安装包 Mathematica - 12.1 安装 解压软件包 7z x Mathematica_12.1.1_LINUX_CN.zip运行安装器 命令运行后解压出Mathematica_12.1.1_LINUX_CN.sh, 运行该安装脚本 chmod x Mathematica_12.1.1_LINUX_CN…

STM32后备区域:读写BKP备份寄存器与使用RTC实时时钟详解

目录 STM32后备区域:读写BKP备份寄存器与使用RTC实时时钟详解 1 什么是STM32的后备区域 分割线* 2.1 BKP备份寄存器简介 2.2 BKP备份寄存器基本结构 2.3 BKP外设头文件 bkp.h介绍 2.4 读写 BKP备份寄存器 操作步骤 2.5 编写 读写备份寄存器 5.1 文件介绍 …

Centos7 系统下安装go语言开发环境

该文章简述在Centos7 amd64 系统中安装go开发环境的方法。 一、golang官网查看对应平台最新的golang版本 Golang 官网地址:All releases - The Go Programming Language 二、 安装GO的过程及相关命令 # 1、下载go,这里使用 go1.22.5 版本,可…

【ACL2024】基于长尾检索知识增强的大语言模型

近日,阿里云人工智能平台PAI与阿里集团安全部内容安全算法团队、华东师范大学何晓丰教授团队合作,在自然语言处理顶级会议ACL2024上发表论文《On the Role of Long-tail Knowledge in Retrieval Augmented Large Language Models》,论文主题为…

爆火游戏《黑神话:悟空》研发背后有哪些故事?

极狐GitLab 是 GitLab 在中国的发行版,专门面向中国程序员和企业提供企业级一体化 DevOps 平台,用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规,而且所有的操作都是在一个平台上进行,省事省心省钱。可以一键安装极狐GitL…

美团的测试面试题,真的很难吗?

年前,我的一个粉丝留言给我说,他在面试美团的自动化测试岗的时候,不幸挂掉了。 越想越可惜,回想面试经过,好好总结了几个点,发现面试没过的主要原因是在几个关键的问题没有给到面试官想要的答案 美团的面…

寻访中国100家.NET中大企业 —— 第二站:苏州行

一:事情起因 在.NET圈里混了十多年,相信有不少人知道我专注于玩 .NET高级调试,如今技术上的硬实力还是能够解决市面上的一些疑难杂症,但软实力却在另一个极端,如(人际交往,人情事故&#xff09…

[RCTF2015]EasySQL1

打开题目 点进去看看 注册个admin账户,登陆进去 一个个点开看,没发现flag 我们也可以由此得出结论,页面存在二次注入的漏洞,并可以大胆猜测修改密码的源代码 resoponse为invalid string的关键字是被过滤的关键字,Le…

氟化工特氟龙精馏装置:PFA氟化氢反应装置的应用

精馏装置是进行精馏的一种塔式气液接触装置。利用混合物中各组分具有不同的挥发度,即在同一温度下各组分的蒸气压不同这一性质,使液相中的轻组分(低沸物)转移到气相中。 实验精馏装置的组成 实验精馏装置通常由以下几部分组成&am…

Linux2.6设备驱动开发

一:Linux2.6驱动设备开发的特点 1:首先是属于字符型设备注册的方法之一 这种开发接口是在Linux2.6引入的,之前的版本不支持这种开发方式,也是目前最标准的开发方式。 2:Linux2.6的设备开发 不再去限制设备号&#xf…

(javaweb)SpringBootWeb案例(毕业设计)案例--部门管理

目录 1.准备工作 2.部门管理--查询功能 3.前后端联调 3.部门管理--新增功能 1.准备工作 mapper数据访问层相当于dao层 根据页面原型和需求分析出接口文档--前后端必须遵循这种规范 大部分情况下 接口文档由后端人员来编写 前后端进行交互基于restful风格接口 http的请求方式…

TypeScript学习笔记(二)——TypeScript 高级类型

目录 1. class 类 1.1 class 关键字的基本使用 1.2 类继承 1.3 类成员可见性 1.4 类成员只读修饰符 2. 类型兼容性 2.1 类型兼容性 2.2 接口兼容性 2.3 函数兼容性 3. 交叉类型 4. 泛型 4.1 创建泛型函数 4.2 泛型约束 4.3 多个泛型的类型变量约束 4.4 泛型接口…

【深度学习入门项目】基于支持向量机的手写数字识别

目录 导入必要的包1. 数据集2. 数据处理3. 训练过程4. 输出结果完整代码 本项目使用SVM训练模型,用于预测手写数字图片。 导入必要的包 numpy: 这个库是Python中常用的数学计算库。在这个项目中,我使用numpy来处理图像数据,将图像数据转换为…

FPGA开发——DS18B20读取温度并且在数码管上显示

一、简介 在上一篇文章中我们对于DS18B20的相关理论进行了详细的解释,同时也对怎样使用DS18B20进行了一个简单的叙述。在这篇文章我们通过工程来实现DS18B20的温度读取并且实现在数码管伤显示。 1、基本实现思路 根据不同时刻的操作,我们可以使用一个状…

基于vue框架的班级网站的设计与实现vg66m(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能:班级,学生,班级活动,班级相册,班级开支,活动记录 开题报告内容 基于Vue框架的班级网站设计与实现 开题报告 一、引言 随着互联网技术的飞速发展,网络已经成为人们日常生活中不可或缺的一部分。在教育领域,班级…