当前位置: 首页 > 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;我有一句话叫低代码…...

OpenLayers 可视化之热力图

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 热力图&#xff08;Heatmap&#xff09;又叫热点图&#xff0c;是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

python执行测试用例,allure报乱码且未成功生成报告

allure执行测试用例时显示乱码&#xff1a;‘allure’ &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;ڲ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ⲿ&#xfffd;&#xfffd;&#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;ǿ&#xfffd;&am…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

免费数学几何作图web平台

光锐软件免费数学工具&#xff0c;maths,数学制图&#xff0c;数学作图&#xff0c;几何作图&#xff0c;几何&#xff0c;AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...