设计模式学习笔记 - 设计原则 - 1.单一职责原则
前言
前面我们提到过 SOLID 原则,实际上 SOLID 由 5 个设计原则组成,分别是:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖反转原则。它们分别对应 SLOID 中的 S、O、L、I、D 这 5 个英文字母。
今天来学习下 SOLID 原则中的第一个原则:单一职责原则。
如何理解单一职责原则(SRP)
单一职责原则 (Single Responsibility Principle),缩写为 SRP。
英文原文的描述:A class or module should have a single responsibility。翻译成中文就是,一个类或模块只负责完成一个职责或功能。
注意,这个原则描述的对象包含两个,一个是类,一个是模块。不管是哪个对象,单一职责在应用到这两个面对对象的时候,道理都是相通的。接下来,我会只从“类”设计的角度,来讲解如何应用这个原则。
一个类只负责完成一个职责或者功能。也就是说,不要设计大而全的类,要设计粒度小、功能单一的类。要把大而全的类拆分成多个功能更加单一、粒度更细的类。
换个角度来说,一个类包含了两个或者两个以上业务不相干的功能,就不符合单一职责原则。
例如,一个类里面包含订单的一些操作,又包含用户的一些操作。而订单和用户是两个独立的业务领域模型,我们将两个不相干的功能放到同一个类中,就违反了单一职责原则。为了满足单一职责原则,需要将这个类拆分成两个粒度更细、功能更单一的两个类:订单类和用户类。
如何判断类的职责是否足够单一?
从刚刚的例子来看,单一职责原则看似不难应用。那是因为我举的例子比较极端,一眼就能看出订单和用户毫不相干。但大部分情况下,类里的方法是归类为同一类功能,还是归为不相关的两类功能,并不是那么容易判定的。在真实的软件开发中,对一个类是否职责单一的判定,是很难拿捏的。
在一个社交产品中,用下面的 UserInfo 来记录用户的信息。你觉得是否符合单一职责原则呢?
public class UserInfo {private long userId;private String username;private String email;private String telephone;private long createTime;private long lastLoginTime;private String avatarUrl;private String provinceOfAddress; // 省private String cityOfAddress; // 市private String regionOfAddress; // 区private String detailOfAddress; // 详细地址// 省略其他属性和方法...
}
对于这个问题,有两种不同的观点。
- 一种观点是,
UserInfo类包含的都是跟用户相关的信息,所有的属性和方法都隶属于用户这样一个业务模型,满足单一职责原则; - 另一种观点是,地址信息在
UserInfo类中,所占比重较高,可以继续拆分成独立的UserAddress,UserInfo只保留出UserAddress之外的属性,拆分之后的两个类的职责更加单一。
实际上,要做出选择,我们不能脱离具体的应用常见。如果,在这个社交产品中,用户地址信息和其他信息一样,只是单纯地用于展示,那 UserInfo 现在的设计就是合理的。但是,如果这个社交产品发展地比较好,之后又在产品中添加了电商的模块,用户的地址信息还会用在电商物流中,那我们最好将地址信息从 UserInfo 中拆分出来,独立成用户物流信息。
再进一步延伸下,如果做这个社交产品的公司发展地越来越好,公司内部有开发出了很多其他产品。公司希望支持统一账号系统,也就是用户一个账号可以在公司内的所有产品中登录。这个时候,我们就需要对 UserInfo 进行拆分,将跟身份认证相关的信息(比如 email、telephone 等)抽取成独立的类。
从上面的例子,可以总结出,在不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,都是不一样的。在某种应用场景或者当下的需求背景下,一个类的设计可能符合单一职责原则了,但是如果换个应用场景或者未来的某个需求背景下,可能就不满足了,需要继续拆分成粒度更细的类。
此外,从不同的业务层面去看待同一个类的设计,对类是否职责单一,也会有不同的认识。比如,例子用的 UserInfo 类。如果从“用户”这个业务层面来看,UserInfo 包含的信息都属于用户,满足单一职责原则。如果从更加细分的“用户展示信息” “地址信息” “登录认证信息”等等这些细细粒度的业务层面来看,那 UserInfo 就应该继续拆分。
综上所述,评价一个类的职责是否足够单一,我们并没有非常明确的、可以量化的标准。实际上,在真正的软件开发中,也没必要过于未雨绸缪,过度设计。所以,我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这个就是所谓的重构。
这里还有些小技巧,能够辅助你,从侧面判断一个类的职责是否足够单一:
- 类中的代码行数、函数或属性过多,会影响代码的可读性和可维护性,我们就需要考虑读类进行拆分。
- 类依赖的其他类过多,或者依赖的类的其他类过多,不符合高内聚、低耦合的设计思想,就需要考虑拆分。
- 私有方法过多,就要考虑是否将私有方法独立到新的类中,设置为 public 方法,供更多的类适用,从而提高代码的复用性。
- 比较难给类起合适的名字,很难用一个业务名词概括,或者只能用一些笼统的
Manager、Context之类的词语来命名,这就说明类的职责定义得可能不够清楚。 - 类中的大量方法都是集中操作类中的某几个属性,比如,在
UserInfo中,如果有一办的方法都是在操作address信息,那就可以考虑将这几个属性和对应的方法拆分出来。
不过,你可能有这样的疑问:在上面的小技巧中,提到的类的代码行数、函数或者属性过多,就有可能不满足单一职责原则。那多少行代码才算是行数过多呢?多少个函数、属性才称得上过多呢?
实际上,这个问题,很不好定量的回答。实际上,可以给你一个凑活能用的、比较宽泛的、可量化的标准,那就是一个类的代码行数最好不能超过 200 行,函数及属性的个数最好不要超过 10 个。
实际上,从另一种角度来看,当一个类的代码,读起来让你头大了,实现某个功能时不知道该用哪个函数了,想用哪个函数翻半天找不到了,只能用到一个小功能却要引入整个类的时候,这说明类的行数、函数、属性过多了。
类的职责是否设计地越单一越好?
为了满足单一职责,是不是把类拆得越细就越好呢?答案是否定的。通过一个例子来解释下。 Serialization 类实现了一个简单协议的序列化和反序列化功能,具体代码如下:
/*** protocol format: identifier-string;(gson string)* For example: UEUEUE; {"a":"A", "b":"B"}*/
public classs Serialization {private static final String IDENTIFIER_STRING = "UEUEUE;";private Gson gson;public Serialization() {this.gson = new Gson();}public String serialize(Map<String, String> object) {StringBuilder textBuilder = new StringBuilder();textBuilder.append(IDENTIFIER_STRING);textBuilder.append(gson.toJson(object));return textBuilder.toString();}public Map<String, String> deserialize(String text) {if(!text.startsWith(IDENTIFIER_STRING)) {return Collections.emptyMap();}String gsonStr = text.substring(IDENTIFIER_STRING.length());return gson.fromJson(gsonStr, Map.class);}
}
如果我们想让类的职责更加单一,我们对 Serialization 类进一步拆分,拆分成一个只负责序列化工作的 Serializer 类和另一个只负责反序列化工作的 Deserializer 类。拆分后的代码如下所示:
public classs Serializer {private static final String IDENTIFIER_STRING = "UEUEUE;";private Gson gson;public Serializer() {this.gson = new Gson();}public String serialize(Map<String, String> object) {StringBuilder textBuilder = new StringBuilder();textBuilder.append(IDENTIFIER_STRING);textBuilder.append(gson.toJson(object));return textBuilder.toString();}
}public classs Deserializer {private static final String IDENTIFIER_STRING = "UEUEUE;";private Gson gson;public Deserializer() {this.gson = new Gson();}public Map<String, String> deserialize(String text) {if(!text.startsWith(IDENTIFIER_STRING)) {return Collections.emptyMap();}String gsonStr = text.substring(IDENTIFIER_STRING.length());return gson.fromJson(gsonStr, Map.class);}
}
虽然经过拆分之后,Serializer 类和 Deserializer 类的职责更加单一了,但也随之带来了新的问题。如果我们修改了协议的格式,数据标识从 UEUEUE 改成了 DFDFDF,或者序列化方式从 JSON 改成了 XML,那 Serializer 类和 Deserializer 类都需要做相应的改动,代码的内聚性显然没有原来的 Serialization 高了。而且,如果我们仅仅对 Serializer 类做了协议修改,而忘记了修改 Deserializer 类,那就会导致序列化和反序列化不匹配,程序运行出错,拆分之后,代码的可维护性变差了。
实际上,不管是应用设计原则还是设计模式,最终的目的还是提高代码的可读性、可扩展性、可维护性、复用性等。我们在考虑应用某一设计原则是否合理的时候,也可以以此作为最终的考量标准。
回顾
1.如何理解单一职责原则(SRP)?
一个类只负责完成一个职责或功能。不要设计大而全的类,要设计粒度小、功能单一的类。单一职责原则是为了提高代码的高内聚、低耦合,提高代码的复用性、可读性、可维护性。
2.如何判断类的职责是否足够单一?
不同的应用场景、不同阶段的需求、不同的业务背景,对同一个类的职责是否单一,可能会有不同的判定结果。实际上,一些侧面的指标更具有指导意义和可执行性,比如,出现下面这些情况,就有可能说明这个类的设计不满足单一职责原则:
- 类中的代码行数、函数、属性过多。
- 类依赖的其他类过多,或者依赖类的其他类过多。
- 私有方法过多。
- 比较难给一个类起一个合适的名字。
- 类中的大量方法都是集中操作类中的几个属性。
3.类的职责是否设计的越细越好?
单一职责原则通过避免设计大而全的类,避免将不相关的功能聚合在一起,来提高类的高内聚性。同时,类职责单一,类依赖和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、低耦合。但是,如果拆分地过细,实际上会适得其反,反倒会降低代码的内聚性,也会影响代码的可维护性。
相关文章:
设计模式学习笔记 - 设计原则 - 1.单一职责原则
前言 前面我们提到过 SOLID 原则,实际上 SOLID 由 5 个设计原则组成,分别是:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖反转原则。它们分别对应 SLOID 中的 S、O、L、I、D 这 5 个英文字母。 今天来学习下 SOLID 原则中的第…...
飞天使-学以致用-devops知识点4-SpringBoot项目CICD实现(实验失败,了解大概流程)
文章目录 代码准备创建jenkins 任务测试推送使用项目里面的jenkinsfile 进行升级操作 文字版本流程项目构建 代码准备 推送代码到gitlab 代码去叩叮狼教育找 k8s 创建jenkins 任务 创建一个k8s-cicd-demo 流水线任务 将jenkins 里面构建时候的地址还有token, 给到…...
使用HTML5画布(Canvas)模拟图层(Layers)效果
使用HTML5画布(Canvas)模拟图层(Layers)效果 在图形处理和计算机图形学中,图层(Layers)是指将图像分成不同的可独立编辑、组合和控制的部分的技术或概念。每个图层都可以包含不同的图形元素、效…...
违背祖训,微软骚操作强制用户更新至 Win 11 23H2
话说,大伙儿有让 Windows 操作系统一直保持最新版习惯吗? 根据以往惯例,Windows 系统更新是个比较玄学的存在,谁也不能保证随手更新后会不会出现什么奇葩 Bug。 因此对于不少同学来说,Windows 更新到一个稳定版本后&a…...
MISRA C++ 2023指南:您需要了解的一切
MISRA C 2023可以帮助使用现代C语言的组织开发安全关键型软件。使用新的MISRA标准,开发人员可以通过确保和记录其软件应用程序的MISRA合规性,满足IEC 6108或ISO 26262等功能安全标准给出的静态分析要求。 什么是MISRA C2023? 以便使用C17进行安全可靠…...
Vue:【亲测可用】父组件数组包对象,传给子组件对象,子组件修改属性(字段)后,父组件没有更新
场景:vue中父组件数组包对象,传给子组件对象,子组件修改属性(字段)后,父组件没有更新 代码: # 父组件 <div v-for"(object, name, index) in arr" :key"index"><…...
hbase学习十:客户端实现与Meta表解析
1、客户端实现 hbase社区的客户端一般是java客户端。 HBase也支持Shell交互式客户端。Shell客户端实质是用JRuby(用Java编写的Ruby解释器,方便Ruby脚本跑在JVM虚拟机上)脚本调用官方HBase客户端来实现的。因此,各种客户端的核心实现都在社区Java版本客户端上。 客户端访…...
《OpenScene: 3D Scene Understanding with Open Vocabularies》阅读笔记1
传统的3D场景理解方法依赖于带标签的3D数据集,用于训练一个模型以进行单一任务的监督学习。我们提出了OpenScene,一种替代方法,其中模型在CLIP特征空间中预测与文本和图像像素共同嵌入的3D场景点的密集特征。这种零样本方法实现了与任务无关的训练和开放词汇查询。例如,为了…...
数据结构 - Trie树(字符串统计、最大异或对)
文章目录 前言Part 1:Trie字符串统计1.题目描述输入格式输出格式数据范围输入样例输出样例 2.算法 Part 2:最大异或对1.题目描述输入格式输出格式数据范围输入样例输出样例 2.算法 前言 本篇博客将介绍Trie树的常见应用,包括:Trie…...
2. vue 工程创建
1. 基于 vite创建 官方文档: https://v3.cn.vuejs.org/guide/installation.html#vite vite官网: https://vitejs.cn 使用vite创建的优势: 开发环境中,无需打包操作,可快速的冷启动。轻量快速的热重载(HMR)。真正的按需编译,不再…...
2024绿色能源、城市规划与环境国际会议(ICGESCE 2024)
2024绿色能源、城市规划与环境国际会议(ICGESCE 2024) 一、【会议简介】 随着全球气候变化和环境问题日益严重,绿色能源和可持续发展已成为全球关注的焦点。本次会议旨在汇聚全球在绿色能源、城市规划与环境领域的专家、学者和实践者,共同探讨和分享关于…...
0门槛电子画册制作
电子画册制作,门槛低至零,也可以制作出如此精美的电子画册吗?别担心,这个问题早已解决,今天就教你如何0门槛制作电子画册。 选择合适的企业宣传册制作软件,如FLBOOK在线制作电子杂志平台等。这个工具提供…...
C语言----冒泡排序进阶
冒泡排序大家应该到写过吧。但大家可能知道到的冒泡排序有两种方法。而我呢,最近学习到了另外一种方法,现在知道三种方法了。所以想与大家分享一下。但是缺点是第三种是第二种的自实现版。第一种就是我们平常写的普通冒泡排序。第二种就是qsort。第三种就…...
【机器学习】实验5,AAAI 会议论文聚类分析
本次实验以AAAI 2014会议论文数据为基础,要求实现或调用无监督聚类算法,了解聚类方法。 任务介绍 每年国际上召开的大大小小学术会议不计其数,发表了非常多的论文。在计算机领域的一些大型学术会议上,一次就可以发表涉及各个方向…...
安卓虚拟机ART和Dalvik
目录 一、JVM和Dalvik1.1 基于栈的虚拟机字节码指令执行过程 1.2 基于寄存器的虚拟机 二、ART与Dalvikdex2aotAndroid N的运作方式 三、总结 一、JVM和Dalvik Android应用程序运行在Dalvik/ART虚拟机,并且每一个应用程序对应有一个单独的Dalvik虚拟机实例。 Dalvik…...
OPENWRT本地局域网模拟域名多IP
本地配置MINIO服务时,会遇到域名多IP的需求。当某一个节点失效时,可以通过域名访问平滑过渡到其它的节点继续服务。 【MINIO搭建过程略】 搭建完毕后,有4个节点,对应的docker搭建命令: docker run --nethost --rest…...
今日学习总结2024.3.2
最近的学习状态比较好,感觉非常享受知识进入脑子的过程,有点上头。 实验室一个星期唯一一天的假期周六,也就是今天,也完全不想放假出去玩啊,在实验室泡了一天。 很后悔之前胆小,没有提前投简历找实习&…...
Java虚拟机(JVM)从入门到实战【上】
Java虚拟机(JVM)从入门到实战【上】,涵盖类加载,双亲委派机制,垃圾回收器及算法等知识点,全系列6万字。 一、基础篇 P1 Java虚拟机导学课程 P2 初识JVM 什么是JVM Java Virtual Machine 是Java虚拟机。…...
SaaS 电商设计 (九) 动态化且易扩展的实现购物车底部弹层(附:一套普适的线上功能切量的发布方案)
目录 一.背景1.1 业务背景1.2 技术负债 二.技术目标三.方案设计3.1 解决移动端频繁发版3.1.1 场景分析3.1.2 技术方案 3.2 减少后端坏味道代码&无法灵活扩展问题3.2.1 通过抽象接口完成各自单独楼层渲染逻辑3.2.2 通过配置能力做到部分字段可配 四.升级上线(普适于高并发大…...
数据结构——lesson5栈和队列详解
hellohello~这里是土土数据结构学习笔记🥳🥳 💥个人主页:大耳朵土土垚的博客 💥 所属专栏:数据结构学习笔记 💥对于顺序表链表有疑问的都可以在上面数据结构的专栏进行学习哦~感谢大家的观看与…...
OpenClaw学习助手:Qwen3.5-9B生成Anki记忆卡片与错题集
OpenClaw学习助手:Qwen3.5-9B生成Anki记忆卡片与错题集 1. 为什么需要AI驱动的学习助手? 作为一名经常需要记忆大量知识点的学生,我一直在寻找更高效的学习方法。传统的手工制作Anki卡片不仅耗时耗力,而且很难保证知识点的系统性…...
mutt-wizard疑难排解终极指南:常见错误与解决方案完全清单
mutt-wizard疑难排解终极指南:常见错误与解决方案完全清单 【免费下载链接】mutt-wizard A system for automatically configuring mutt and isync with a simple interface and safe passwords 项目地址: https://gitcode.com/gh_mirrors/mu/mutt-wizard mu…...
如何在openKylin 2.0 SP2中安装Qt(v0.2.2)(上)
作者:沈传越,赵文硕 明德融创工作室(Minter Fusion Studio, MFS) 出品 本文的所有步骤均经过测试复现 如何在openKylin 2.0 SP2中安装Qt(v0.2.2)(下) Qt是一款著名的桌面图形化系…...
AI写论文就选它们!4个AI论文写作工具,搞定期刊论文写作!
撰写期刊论文、毕业论文或职称论文时,学术朋友们常常会遇到不少挑战。自己动手写论文时,面对大量的学术文献,寻找相关资料简直像在大海捞针;而繁琐的格式要求又让人应接不暇,恨不得抓狂;一遍又一遍的修改&a…...
中小工厂人手少、员工文化不高,选这款ERP,工人半天就能学会
开中小工厂最头疼的是什么?规模不大、人手有限,车间工人、仓库管理员文化水平不高,想上 ERP 管生产、管库存,又怕太复杂学不会、用不起来。其实不用纠结,选对软件,普通员工也能快速上手,今天就给…...
“.NET 11 + ONNX Runtime 1.18 + Triton集成”三重加速组合拳:某全球Top3药企临床辅助诊断系统P99延迟压至17ms的完整链路揭秘
第一章:“.NET 11 ONNX Runtime 1.18 Triton集成”三重加速组合拳:某全球Top3药企临床辅助诊断系统P99延迟压至17ms的完整链路揭秘该系统面向高并发、低延迟的病理图像实时推理场景,需在单次请求中完成多模态(HE染色切片免疫组化…...
Infoseek舆情系统决策树:在回应、沉默与引导间寻找最优解
对于许多品牌公关从业者而言,最难熬的时刻并非负面舆情爆发时的焦头烂额,而是事件初露端倪时的犹豫不决。手里攥着Infoseek舆情系统推送的早期预警,看着那条曲线正在缓慢抬头,一个终极难题摆在面前:是立刻回应以求先发…...
UMS3 Helper:ESP32-S3开发板硬件抽象库详解
1. UMS3 Helper 库概述UMS3 Helper 是为 Unexpected Maker 全系列 ESP32-S3 开发板量身定制的底层硬件抽象辅助库,覆盖 NanoS3、OMGS3、TinyS3、ProS3、FeatherS3 及 FeatherS3 Neo 六款主流型号。该库并非通用型驱动框架,而是深度耦合各板载外设物理布局…...
终极Windows风扇控制解决方案:FanControl深度配置与性能优化实战指南
终极Windows风扇控制解决方案:FanControl深度配置与性能优化实战指南 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitH…...
【Dv3Admin】Django一键配置权限规则
源码中的角色—菜单—按钮—字段权限控制,往往是后台系统中最容易被忽略、却最容易出问题的部分。一旦权限粒度设计不清晰,就会出现按钮越权、字段泄露、前端渲染混乱等一系列连锁问题,这类问题通常并非单点错误,而是接口设计与数…...
