面向对象设计之单一职责原则
设计模式专栏:http://t.csdnimg.cn/6sBRl
目录
1.单一职责原则的定义和解读
2.如何判断类的职责是否单一
3.类的职责是否越细化越好
4.总结
1.单一职责原则的定义和解读
单一职责原则(Single Responsibility Principle,SRP)的描述:一个类或模块只负责完一个职责(或功能)(A class or module should have a single reponsibility)。
注意,单一职责原则描述的对象有两个:类(class)和模块(module)。关于这两个概念我们有两种理解方式。一种理解方式是把模块看作比类更加抽象的概念,把类看作一种模块;另一种理解方式是把模块看作比类更粗粒度的代码块,多个类组成一个块。
无论哪种理解方式,单一职责原则在应用这两个描述对象时,原理是相通的。为了方讲解,我们只从“类”设计的角度讲解如何应用单一职责原则。对于“模块”,读者可以自行理解。
单一职责原则是指一个类负责完成一个职责或功能。也就是说,我们不要设计大而全的类,要设计粒度小、功能单一的类。换个角度来讲,如果一个类包含两个或两个以上业务不相干的功能,那么我们就可以认为它的职责不够单一,应该将其拆分成多个粒度更小的功能单一的类。
例如,某类既包含对订单的一些操作,又包含对用户的一些操作。而订单和用户是两个独立的业务领域模型,将两个不相干的功能放到同一个类中,就违反了单一职责原则。为了满足单一职责原则,我们需要将这个类拆分成粒度更小的功能单一的两个类:订单类和用户类。
2.如何判断类的职责是否单一
在上章节举的例子简单,我们立即就能看出订单和用户毫不相干。但大部分情况下,类中的方法是归为同一类功能,还是归为不相关的两类功能,并不是那么容易判定。在真实的软件开发中,一个类是否职责单一的判定是很难的。我们用一个贴近真实开发的例子来解释类的职责是否单一的判定问题。
在某个社交产品中,我们用UserInfo类记录用户信息、那么,读者觉得以下 UserInfo类的设计是否满足单一职责原则呢?
public class UserInfo {private long userId;private String username,private String email;private String telephone;private long createTime;private long lastloginrime;private String avatarUrl;private String provinceofAddress; //省private String cityofaddress; //市private String regionofAddress; //private String detailedAddress;//详细地址//...省略其他属性和方法...
}
对于这个问题,我们有两种不同的观点。一种观点是UserInfo类包含的是与用户相关的信息,所有的属性和方法都隶属于用户这样一个业务模型、满足单一职责原则;另一种观点是地址信息在UserInfo类中所占的比例较高,可以继续拆分成独立的UserAddress类,而UserInfo只保留除地址信息之外的其他信息,拆分后的两个类的职责变得单一。
对于上述两种观点,哪种观点是合理的呢?实际上,如果我们想要从中做出选择,就不能脱离具体的应用场景。如果在这个社交产品中,用户的地址信息与用户其他信息一样,只是用来进行信息展示,它们同时被使用,那么UserInfo类目前的设计就是合理的。但是,假如这个社交产品发展得比较好,之后又在该产品中添加了电商功能模块,用户的地址信息不仅用于展示,还会独立地应用在电商的物流中,此时最好将地址信息从Userlnfo类中拆分出来,独立成为物流信息(或者称为地址信息、收货信息等)。
再进一步,假如这个社交产品所属的公司发展壮大,该公司又开发了很多其他产品(可以理解为其他App)。该公司希望其所有产品支持统一账号系统,即用户使用同一个账号可以在该公司的所有产品登录。此时,就需要继续对UserInfo类进行拆分,将与身份认证相关的信息(如email、telephone等)抽取成独立的类。
从上面的例子中,我们总结得出:在不同的应用场景和不同阶段的需求背景下,对同一个类的职责是否单一的判定可能是不一样的。在某种应用场景或当下的需求背景下,一个类的设计可能已经满足单一职责原则了,但如果换个应用场景或在未来的某个需求背景下,就可能不满足单一职责原则了,需要继续拆分成粒度更小的类。
除此之外,在从不同的业务层面看同一个类的设计时,我们对类是否职责单一的判定会有不同的认识。对于上面例子中的 UserInfo类,如果从“用户”业务层面来看,UserInfo 类包含的信息都属于用户,那么满足职责单一原则;如果从“用户展示信息”、“地址信息”、“登录认证信息”等更细粒度的业务层面来看,那么UserInfo 类就不满足单一职责原则,应该继续拆分。
综上所述,评价一个类的职责是否单一,并没有一个明确的、可量化的标准。实际上,在真正的软件开发中,我们没必要过度设计(粒度过细)。我们可以先编写一个粗粒度的类,满足当下的业务需求即可。随着业务的发展,如果这个粗粒度的类越来越复杂,代码越来越多,那么我们在这时再将这个粗粒度的类拆分成几个细粒度的类即可。
对于职责是否单一的判定,存在一些判定规则,如下所示:
1) 如果类中的代码行数、函数或属性过多,影响代码的可读性和可维护性,就需要考虑对类进行拆分。
2) 如果某个类依赖的其他类过多,或者依赖某个类的其他类过多,不符合高内聚、低合的代码设计思想,就需要考虑对该类进行拆分。
3) 如果类中的私有方法过多,就需要考虑将私有方法独立到新的类中,并设置为public方法,供更多的类使用,从而提高代码的复用性。
4) 如果类很难准确命名(很难用的个业务名词概括),或者只能用 Manager、Context之类的笼统的词语来命名,就说明类的职责定义不够清晰。
5) 如果类中的大量方法集中操作其中几个属性(如上面的UserInfo类的例子中,加入很多方法只操作 address 信息),就可以考虑将这些属性和对应的方法拆分出来。
3.类的职责是否越细化越好
为了满足单一职责原则,是不是把类拆分得越细就越好呢?答案是否定的、我们举例解释,示例代码如下所示。Serialization 类实现了一个简单协议的序列化和反序列功能。
/***Protocol format:identifer-string;(gson string)*For example: UUEUE;{"a":"A" "b":"B"}*/
public class 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 class Serializer{private static final String IDENTIFIER_STRING = "UEUEUE; ";private Gson gson;public Serializer(){thís.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 class 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 类都需要做相应的修改,代码的内聚性显然没有之前高了。而且,如果我们对 Serializer 类做了协议修改,而忘记修改Deserializer 类的代码,就会导致序列化和反序列化不匹配,程序运行出错,也就是说,拆分之后,代码的可维护性变差了。
实际上,无论是应用设计原则还是设计模式,最终的目的都是为了提高代码的可读性、可扩展性、复用性和可维护性等。在判断应用某一个设计原则是否合理时,我们可以以此作为最终的评价标准。
4.总结
单一职责原则的核心思想是:一个类或者一个模块应该只负责一个功能领域中的相应职责,或者说,一个类应该只有一个引起它变化的原因。
在软件系统中,如果一个类(无论是模块、方法还是其他更小的单元)承担的职责过多,那么这个类的复用性就会降低,因为不同的职责可能对应着不同的使用场景。此外,当这个类的某个职责发生变化时,可能会影响到其他职责的运作,从而增加出错的可能性。因此,根据单一职责原则,我们应该将不同的职责分离,并将它们封装在不同的类中。
例如,在微服务架构中,每个微服务通常只负责一个或少数几个紧密相关的功能,这就是单一职责原则的具体实践。通过将复杂的业务拆分成多个功能单一的微服务,我们可以提高系统的可维护性、可扩展性和可复用性。
总的来说,单一职责原则有助于我们设计出更加灵活、易于维护和扩展的软件系统。在实际编程中,我们应该时刻注意遵守这一原则,避免将过多的职责集中在一个类中。
相关文章:
面向对象设计之单一职责原则
设计模式专栏:http://t.csdnimg.cn/6sBRl 目录 1.单一职责原则的定义和解读 2.如何判断类的职责是否单一 3.类的职责是否越细化越好 4.总结 1.单一职责原则的定义和解读 单一职责原则(Single Responsibility Principle,SRP)的描述:一个类…...

蓝桥杯真题:单词分析
import java.util.Scanner; //1:无需package //2: 类名必须Main, 不可修改 public class Main{public static void main(String[]args) {Scanner sannernew Scanner(System.in);String strsanner.nextLine();int []anew int [26];for(int i0;i<str.length();i) {a[str.charA…...

Python字符串字母大小写变换,高级Python开发技术
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书! ‘’’ demo ‘tHis iS a GOod boOK.’ print(demo.casefold()) print(demo.lower()) print(demo.upper()) print(demo.capitalize()) print(demo.title()) print(dem…...
CentOS常用功能命令集合
1、删除指定目录下所有的空目录 find /xxx -type d -empty -exec rmdir {} 2、删除指定目录下近7天之前的日志文件 find /xxx -name "*.log" -type f -mtime 7 -exec rm -f {} \; 3、查询指定目录下所有的指定格式文件(比如PDF文件) find…...

黑马点评项目笔记 II
基于Stream的消息队列 stream是一种数据类型,可以实现一个功能非常完善的消息队列 key:队列名称 nomkstream:如果队列不存在是否自动创建,默认创建 maxlen/minid:设置消息队列的最大消息数量 *|ID 唯一id:…...
关于一篇知乎答案的重现
〇、前言 早上在逛知乎的时候,瞥见了一篇答案:如何通俗解释Docker是什么?感觉很不错,然后就耐着性子看了下,并重现了作者的整个过程。但是并不顺利,记载一下这些坑。嫌麻烦的话可以直接clone 研究…...
实时数据库测试-汇编小程序
实时数据库测试-汇编小程序。 hd.asm .686 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc include \masm32\include\gdi32.inc …...
HTML5 、CSS3 、ES6 新特性
HTML5 新特性 1. 新的语义化元素:article 、footer 、header 、nav 、section 2. 表单增强,新的表单控件:calendar 、date 、time 、email 、url 、search 3. 新的 API:音频(用于媒介回放的 video 和 audio 元素)、图形&#x…...

基于springboot+vue实现的驾校信息管理系统
作者主页:Java码库 主营内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】:Java 【框架】:spring…...
X进制减法(贪心算法C++实现)
题目 进制规定了数字在数位上逢几进一。 X 进制是一种很神奇的进制,因为其每一数位的进制并不固定! 例如说某种 X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制,则 X 进制数 321 转换为十…...

[Windows]服务注册工具(nssm)
文章目录 官网下载地址百度云下载地址NSSM常用命令 使用场景:例如现在我们想开启自动启动一个Java服务,nginx,node等。 官网下载地址 https://nssm.cc/download 百度云下载地址 链接:https://pan.baidu.com/s/111fkBWIS7CTlWIj80Kc8Sg?pwdanan 提取码…...
Xilinx缓存使用说明和测试
Xilinx缓存使用说明和测试 1 BRAM说明2 FIFO说明3 实例测试3.1 代码3.2 仿真本文主要介绍Xilinx FPGA芯片中BRAM和FIFO的使用方法和测试结果,主要针对流接口进行仿真。 1 BRAM说明 BRAM是Xilinx芯片中重要的存储资源,其可配置为单端口RAM/ROM或者双端口RAM/ROM,本文以最复杂…...
LeetCode:2952. 需要添加的硬币的最小数量(贪心 Java)
目录 2952. 需要添加的硬币的最小数量 题目描述: 实现代码与解析: 贪心 原理思路: 2952. 需要添加的硬币的最小数量 题目描述: 给你一个下标从 0 开始的整数数组 coins,表示可用的硬币的面值,以及一个…...

企业员工在线培训系统功能介绍
随着信息技术的飞速发展,企业员工培训方式正逐步从传统的面授转向灵活高效的在线培训。一个综合性的企业员工在线培训系统能够为员工提供多样化的学习资源、便捷的学习途径和有效的学习监督,以下是该系统的主要功能详细介绍: 1. 课程功能 线…...
服了,一线城市的后端都卷成这样了吗!?
声明:本文首发在同名公众号:王中阳Go,未经授权禁止转载。 先听TA的故事 投稿的主人公是一名工作5年的后端开发工程师,最近2年用Golang,之前其他语言。去年春节前被裁员了,各种心酸史,好愁人啊。…...

Qt扫盲-QAssisant 集成其他qch帮助文档
QAssisant 集成其他qch帮助文档 一、概述二、Cmake qch例子1. 下载 Cmake.qch2. 添加qch1. 直接放置于Qt 帮助的目录下2. 在 QAssisant中添加 一、概述 QAssisant是一个很好的帮助文档,他提供了供我们在外部添加新的 qch帮助文档的功能接口,一般有两中添…...
[lesson01]学习C++的意义
学习C的意义 C语言特点 C语言是在实践的过程中逐步完善起来的 没有深思熟路的设计过程残留量过多低级语言的特征 C语言的目标是高效 最终程序执行效率的高效 软件方法论的发展 面相过程程序设计:数据结构 算法 主要解决科学计算问题,用户需求简单而…...

LabVIEW双通道太阳射电频谱观测系统
LabVIEW双通道太阳射电频谱观测系统 开发了一个基于LabVIEW平台开发的双通道高速太阳射电频谱观测系统。该系统实时监测太阳射电爆发,具有随机性、持续时间短、变化快等特点。通过高速信号采集卡实现1.5 GS/s的信号采集,时间分辨率可达4ms,频…...

Trapcode Particular---打造惊艳粒子效果
Trapcode Particular是Adobe After Effects中的一款强大3D粒子系统插件,其能够创造出丰富多样的自然特效,如烟雾、火焰和闪光,以及有机的和高科技风格的图形效果。Trapcode Particular功能丰富且特色鲜明,是一款为Adobe After Eff…...

从0到1利用express搭建后端服务
目录 1 架构的选择2 环境搭建3 安装express4 创建启动文件5 express的核心功能6 加入日志记录功能7 日志记录的好处本节代码总结 不知不觉学习低代码已经进入第四个年头了,既然低代码很好,为什么突然又自己架构起后端了呢?我有一句话叫低代码…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...