深入理解设计原则之单一职责原则(SRP)【软件架构设计】

2023-10-25 10:10

本文主要是介绍深入理解设计原则之单一职责原则(SRP)【软件架构设计】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

系列文章目录

C++高性能优化编程系列
深入理解软件架构设计系列
深入理解设计模式系列
高级C++并发线程编程

在这里插入图片描述
命运是弱者失败的借口,是强者乐此不疲的必修课。
Destiny is the weak's excuse for failure, is required for all the strong enthusiasm.

SRP:单一职责原则

  • 系列文章目录
  • 1、单一职责原则的定义和解读
  • 2、单一职责原则案例解读
    • 2.1、违背单一职责原则反面案例
    • 2.2、违背单一职责原则反面案例 - 解决方案
  • 3、类的职责是否越细化越好
  • 4、如何判断类的职责是否单一
  • 5、小结

1、单一职责原则的定义和解读

单一职责原则(Single Responsibility Principle, SRP)的描述:一个类或模块只负责完成一个职责(或功能)。

注意单一职责原则描述的对象有两个:类(Class)和模块(Module)。关于这两个概念我们有两种理解方式。

  • 一种理解方式把模块看作比类更抽象的概念,把类看作一种模块
  • 另一种理解方式把模块看作更粗粒度的代码块,多个类组成一个模块

无论哪种理解方式,单一职责原则在应用这两个描述对象时,原理是相通的。粒度小,功能单一。

2、单一职责原则案例解读

2.1、违背单一职责原则反面案例

  • 反面案例1: 重复的假象
    某个工资管理程序中的Employee类有三个函数caculatePay()、reportHourse()和save()。

在这里插入图片描述

图1 Employee类

这三个类的函数分别对应的是三类非常不同的行为者,违反了SRP设计原则。

caculatePay()函数是由财务部门制定的,他们负责向CFO汇报。
reportHourse()函数是由人力资源部制定并使用的,他们负责向COO汇报。
save()函数是由DBA制定的,他们负责向CTO汇报。

这三个函数被放在同一个源代码文件,即同一个Employee类中,程序员这样做实际就等于使三个类行为耦合在了一起。这有可能会导致CFO团队的命令影响到COO团队所依赖的功能。

例如,caculatePay()函数和reportHourse()函数使用同样的逻辑来计算工作时数。程序员为了避免重复编码,通常会将该算法单独实现一个名为的reportHourse()函数。
在这里插入图片描述

图2 算法共享

接下来,假设CFO团队需要修改正常工作时数的计算方法,而COO带领的HR团队不需要这个修改,因为他们对数据的用法是不同的。
这时候,负责这项修改的程序员会注意到函数调用了函数,但可能不会注意到该函数会同时被调用。

于是,该程序员就这样按照要求进行了修改,同时CFO团队的成员验证了新算法工作正常。这项修改最终被成功部署上线。

但是,COO团队显然完全不知道这些事的发生,HR仍然在使用产生的报表,随后就发现他们的数据出错了!最终这个问题让COO十分愤怒,因为这些错误的数据给给公司造成了几百万的损失。
与此类的事情我们多多少少都经历过。这类问题发生的根源就是因为我们将不同的行为所依赖的代码强凑到一起。对此,SRP强调这类代码一定要分开。

  • 反面案例2: 代码合并
    一个拥有很多函数的源代码文件必然经历很多次代码合并,该文件中的这些函数分别服务不同行为者的情况就更加常见了。

例如,CTO团队的DBA决定要对Emploee数据表结构进行简单修改。与此同时,COO团队的HR需要修改工作时数据报表的格式。

这样一来,就很可能出现两个来自不同团队的程序员分别对Emploee进行修改的情况。不出意外的话,他们各自的修改一定会互相冲突,这就必要进行代码合并。

在这个例子中,这次代码合并不仅可能让CTO和COO要求的功能出错,甚至连CFO原本正常的功能也可能收到影响。

事实上,这样的案例还有很多,我们就不一一例举了。他们的一个共同点是,多人为了一个不同的目的修改了一份源代码,这很容造成问题的产生。

而避免这种问题产生的方法就是将服务不同行为者的代码进行切分。

2.2、违背单一职责原则反面案例 - 解决方案

我们有很多方法可以用来解决上面的问题,每一种方法都需要将相关的函数划分到不同的类。

其中,最简单直接办法是将数据与函数分离,设计三个类共同使用一个不包括函数的、十分简单的EmployeeData类,每个类只包含与之前相关函数的代码,互相不可见,这样就不存在相互依赖的情况了。
在这里插入图片描述

图3 三个类互相不可见

这种解决方案的坏处在于:程序员现在需要在程序里处理三个类。另一种方法是使用Facade(外观)设计模式。
在这里插入图片描述

图4 Facade模式

这样一来,EmployeeFacade类所需要的代码量就很少了,他仅仅包含了初始化和调用三个类的函数。

当然,也有程序员更倾向于把重要的业务逻辑与数据放在一起,那么我们也可以选择将最重要的函数保留在Emploee类中,同时用这个类调其他没那么重要的函数。
在这里插入图片描述

图5 将最重要的函数保留在Emploee类中,同时用这个类调其他没那么重要的函数

读者也许会反对上面这些解决方案,因为看上去这里的每个类中都只有一个函数,事实上并非如此,因为无论是计算工资、生成报表还是保存数据都是一个很复杂的过程,每个类都可能包含了许多私有函数。

总而言之,上面的每一类都分别容纳了一组作用于相同作用域函数,而在作用域之外,它们各自的私有函数是互相不可见的。

3、类的职责是否越细化越好

在面向对象编程中,类的职责不应该过多、过于复杂,而应该越细越好,这是因为:

类的单一职责原则(Single Responsibility Principle):每个类都应该只有一个职责,这样可以保证类的代码简洁明了、易于维护和扩展。

高内聚低耦合原则(High Cohesion Low Coupling):将一个类拆分成多个单一职责的类,可以使得各类之间的耦合度降低,提高代码的灵活性和可复用性。

然而,同时过度细化职责也有其负面影响:

增加代码复杂度和维护成本:当类被拆分成过多微小的类时,会增加代码的数量和复杂度,导致维护成本的增加。

过度抽象将导致代码的不透明性和可读性降低:在面向对象编程中,过度抽象会导致代码难以阅读和理解。

因此,类的职责是否越细化越好,需要根据具体情况进行权衡。在实际编程中,需要保持类的职责尽量单一、明确,但不要过度细化,避免代码的冗余和不必要的复杂性。同时也要注意把握好封装的程度,保证类的内部实现不会对外部造成影响。

4、如何判断类的职责是否单一

要判断一个类的职责是否单一,可以使用以下方法:

  1. 查看类的名称和文档:类的名称和文档应该准确地描述它的职责。如果名称或文档涵盖了多个职责,那么这个类可能不够单一。
  2. 分析类中的方法:观察类中的方法是否都涉及同一个领域或者问题域。如果这些方法处理不同的领域或问题域,那么这个类可能不够单一。
  3. 查看类的属性:观察类中的属性是否都与类的职责相关。如果属性与类的职责无关或者有多个职责,那么这个类可能不够单一。
  4. 观察类的依赖关系:观察类是否依赖其他类或模块,如果这些依赖与类的职责无关或者有多个职责,那么这个类可能不够单一。
  5. 观察代码的复杂度:观察类的代码是否过于复杂,如果代码过于复杂,可能说明这个类的职责不够单一。

综上所述,以上方法可以帮助你判断一个类的职责是否单一。如果你发现一个类的职责过于复杂或者不够单一,那么就需要考虑对这个类进行重构,将其拆分成多个单一职责的类。

5、小结

单一职责原则主要讨论的是函数和类之间的关系 - 但是它在两个讨论层面上会以不同的形式出现。在组件层面上,我们可以将其称为共同闭包原则,在软件架构层面,它则是用于奠定架构边界的变更轴心。

这篇关于深入理解设计原则之单一职责原则(SRP)【软件架构设计】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/281850

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #mermaid-svg-qKD178fTiiaYeKjl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念