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

面向对象设计之单一职责原则

设计模式专栏: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.总结

        单一职责原则的核心思想是:一个类或者一个模块应该只负责一个功能领域中的相应职责,或者说,一个类应该只有一个引起它变化的原因。

        在软件系统中,如果一个类(无论是模块、方法还是其他更小的单元)承担的职责过多,那么这个类的复用性就会降低,因为不同的职责可能对应着不同的使用场景。此外,当这个类的某个职责发生变化时,可能会影响到其他职责的运作,从而增加出错的可能性。因此,根据单一职责原则,我们应该将不同的职责分离,并将它们封装在不同的类中。

        例如,在微服务架构中,每个微服务通常只负责一个或少数几个紧密相关的功能,这就是单一职责原则的具体实践。通过将复杂的业务拆分成多个功能单一的微服务,我们可以提高系统的可维护性、可扩展性和可复用性。

        总的来说,单一职责原则有助于我们设计出更加灵活、易于维护和扩展的软件系统。在实际编程中,我们应该时刻注意遵守这一原则,避免将过多的职责集中在一个类中。

相关文章:

面向对象设计之单一职责原则

设计模式专栏&#xff1a;http://t.csdnimg.cn/6sBRl 目录 1.单一职责原则的定义和解读 2.如何判断类的职责是否单一 3.类的职责是否越细化越好 4.总结 1.单一职责原则的定义和解读 单一职责原则(Single Responsibility Principle&#xff0c;SRP)的描述&#xff1a;一个类…...

蓝桥杯真题:单词分析

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开发技术

寻找有志同道合的小伙伴&#xff0c;互帮互助,群里还有不错的视频学习教程和PDF电子书&#xff01; ‘’’ 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、查询指定目录下所有的指定格式文件&#xff08;比如PDF文件&#xff09; find…...

黑马点评项目笔记 II

基于Stream的消息队列 stream是一种数据类型&#xff0c;可以实现一个功能非常完善的消息队列 key&#xff1a;队列名称 nomkstream&#xff1a;如果队列不存在是否自动创建&#xff0c;默认创建 maxlen/minid&#xff1a;设置消息队列的最大消息数量 *|ID 唯一id&#xff1a;…...

关于一篇知乎答案的重现

〇、前言 早上在逛知乎的时候&#xff0c;瞥见了一篇答案&#xff1a;如何通俗解释Docker是什么&#xff1f;感觉很不错&#xff0c;然后就耐着性子看了下&#xff0c;并重现了作者的整个过程。但是并不顺利&#xff0c;记载一下这些坑。嫌麻烦的话可以直接clone 研究&#xf…...

实时数据库测试-汇编小程序

实时数据库测试-汇编小程序。 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. 新的语义化元素&#xff1a;article 、footer 、header 、nav 、section 2. 表单增强&#xff0c;新的表单控件&#xff1a;calendar 、date 、time 、email 、url 、search 3. 新的 API&#xff1a;音频(用于媒介回放的 video 和 audio 元素)、图形&#x…...

基于springboot+vue实现的驾校信息管理系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…...

X进制减法(贪心算法C++实现)

题目 进制规定了数字在数位上逢几进一。 X 进制是一种很神奇的进制&#xff0c;因为其每一数位的进制并不固定&#xff01; 例如说某种 X 进制数&#xff0c;最低数位为二进制&#xff0c;第二数位为十进制&#xff0c;第三数位为八进制&#xff0c;则 X 进制数 321 转换为十…...

[Windows]服务注册工具(nssm)

文章目录 官网下载地址百度云下载地址NSSM常用命令 使用场景&#xff1a;例如现在我们想开启自动启动一个Java服务,nginx,node等。 官网下载地址 https://nssm.cc/download 百度云下载地址 链接&#xff1a;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. 需要添加的硬币的最小数量 题目描述&#xff1a; 实现代码与解析&#xff1a; 贪心 原理思路&#xff1a; 2952. 需要添加的硬币的最小数量 题目描述&#xff1a; 给你一个下标从 0 开始的整数数组 coins&#xff0c;表示可用的硬币的面值&#xff0c;以及一个…...

企业员工在线培训系统功能介绍

随着信息技术的飞速发展&#xff0c;企业员工培训方式正逐步从传统的面授转向灵活高效的在线培训。一个综合性的企业员工在线培训系统能够为员工提供多样化的学习资源、便捷的学习途径和有效的学习监督&#xff0c;以下是该系统的主要功能详细介绍&#xff1a; 1. 课程功能 线…...

服了,一线城市的后端都卷成这样了吗!?

声明&#xff1a;本文首发在同名公众号&#xff1a;王中阳Go&#xff0c;未经授权禁止转载。 先听TA的故事 投稿的主人公是一名工作5年的后端开发工程师&#xff0c;最近2年用Golang&#xff0c;之前其他语言。去年春节前被裁员了&#xff0c;各种心酸史&#xff0c;好愁人啊。…...

Qt扫盲-QAssisant 集成其他qch帮助文档

QAssisant 集成其他qch帮助文档 一、概述二、Cmake qch例子1. 下载 Cmake.qch2. 添加qch1. 直接放置于Qt 帮助的目录下2. 在 QAssisant中添加 一、概述 QAssisant是一个很好的帮助文档&#xff0c;他提供了供我们在外部添加新的 qch帮助文档的功能接口&#xff0c;一般有两中添…...

[lesson01]学习C++的意义

学习C的意义 C语言特点 C语言是在实践的过程中逐步完善起来的 没有深思熟路的设计过程残留量过多低级语言的特征 C语言的目标是高效 最终程序执行效率的高效 软件方法论的发展 面相过程程序设计&#xff1a;数据结构 算法 主要解决科学计算问题&#xff0c;用户需求简单而…...

LabVIEW双通道太阳射电频谱观测系统

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

Trapcode Particular---打造惊艳粒子效果

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

从0到1利用express搭建后端服务

目录 1 架构的选择2 环境搭建3 安装express4 创建启动文件5 express的核心功能6 加入日志记录功能7 日志记录的好处本节代码总结 不知不觉学习低代码已经进入第四个年头了&#xff0c;既然低代码很好&#xff0c;为什么突然又自己架构起后端了呢&#xff1f;我有一句话叫低代码…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

基于matlab策略迭代和值迭代法的动态规划

经典的基于策略迭代和值迭代法的动态规划matlab代码&#xff0c;实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

论文笔记——相干体技术在裂缝预测中的应用研究

目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术&#xff1a;基于互相关的相干体技术&#xff08;Correlation&#xff09;第二代相干体技术&#xff1a;基于相似的相干体技术&#xff08;Semblance&#xff09;基于多道相似的相干体…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...