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

SOLID原则-单一职责原则

转载请注明出处:https://blog.csdn.net/dmk877/article/details/143447010

作为一名资深程序员越来越感觉到基础知识的重要性,比如设计原则、设计模式、算法等,这些知识的长期积累会让你突破瓶颈实现质的飞跃。鉴于此我决定写一系列与此相关的博客,希望对大家有帮助。提到设计原则可能大家或多或少都听过,单一职责原则、依赖倒置原则、接口隔离原则等,但是你是否对其有深刻的认识或者是否将其应用到真实的项目开发中呢?我先说说我作为一个10多年的程序员的真实感受,因为做Android应用开发在前几年可能更加注重Android基础FrameWork的学习,做了大概5年之后我拿到一个需求仍然花很少时间进行设计,基本都是直接就coding,虽然对设计原则和模式有点了解但是很少在开发需求的时候使用或者说就没有这个意识,慢慢的感觉到已经到了自己的瓶颈期,就是每天都在写代码,但是感觉没啥进步,自己的代码水平跟三年前差不多,于是开始寻求突破,突破的点就是从设计原则、设计模式、算法、Android FrameWork层原理入手,经过几年的学习自己也明显感觉到自己写的代码越来越有层次也慢慢突破了自己的瓶颈,所以我准备写一系列设计原则、设计模式相关的博客,一方面如果自己忘记了能够更快的回忆,一方面大家一起交流讨论从而加深对设计模式的了解。希望大家通过本篇博客的学习能够更加重视设计原则和设计模式,如果你之前的习惯是拿到需求不做设计直接coding那么可以尝试改变一下思维,拿到需求后做下设计将设计原则和模式融入到代码中,这样随着时间的积累你肯定会有很大的进步。

废话不多说,进入正题,在众多的设计原则中比较经典的设计原则莫过于SOLID,SOLID设计原则是五个软件设计核心基本原则的首字母缩写,分别是:

  • 单一职责原则(Single Responsibility Principle, SRP)

  • 开闭原则(Open-Closed Principle, OCP)

  • 里氏替换原则(Liskov Substitution Principle, LSP)

  • 接口隔离原则(Interface Segregation Principle, ISP)

  • 依赖倒置原则(Dependency Inversion Principle, DIP)

SOLID是美国Robert C. Martin提出的,也是《架构整洁之道》的作者,我们今天主要介绍的就是SOLID中的单一职责原则

1 单一职责原则的定义

关于单一职责原则的定义,也是经过了一些演变,在《架构整洁之道》这本书提到,在历史上,我们曾经这样描述SRP这一原则

定义一:一个模块应该有且仅有一个被修改的原因

在现实生活中,软件系统为了满足用户和所有者的要求,必然要做出这样那样的修改。而该系统的用户或者所有者就是该设计原则中所指的"被修改的原因"。

如果一个或多个用户希望对系统进行的变更是相似的,就可以归为一类——一个或多个有共同需求的人。在这里,我们将其称为行为者(actor)。所以对SRP的最终描述就变成了:

定义二:一个模块应该对一类且仅对一类行为者负责。

原文:

Thus the final version of the SRP is:

​ A moudle should be responsible to one, and only one, actor.

这里的"模块"可以看做比类更加抽象的概念,类也可以看做模块。个人觉得两个定义差不太多,都可以用来理解单一职责原则,我们先来看几个生活中的例子

2 单一职责举例-软件公司的角色

在我们的软件开发中大致有这么几个角色,产品、UI、开发、测试,并且这些角色由不同的人承担,大家各司其职,为什么我们不一个人同时担任产品、UI、开发、测试呢?有几个方面的原因

(1)学习成本太高,一个人要同时学习产品、UI、开发、测试的知识,累成狗

(2)可替换性,如果一个人承担很多角色,突然一天他离职了,培训一个承担这么多角色的同事花费的代价太大了,相反如果各司其职,产品离职了招一个专门的产品就行,开发离职招一个专门的开发,花费的代价会小很多

其实在我们开发和架构上,也是这样的。从之前的单体架构,再到现在的微服务架构,我们会发现,服务的粒度越来越小,这样的主要好处就是,如果某个"微服务"出现了异常或者需要升级替换,对整个系统的影响也不会很大

3 单一职责举例-电脑配置

再举一个例子,假如你几年前买了一个台式机,现在你喜欢上了一个游戏,但是当前的电脑配置玩起来卡顿且画面质量差,怎么办呢?去重新买一台吗?当然不需要我们只需要买个大点的内存条+换一个性能好的显卡即可解决此问题,这就是单一职责的好处,电脑的各个配件负责不同的工作,想升级哪个配件只需要单独替换这一个配件即可,是不是非常方便?而且很好维护,如果电脑哪天坏了,维修人员检测到哪个配件坏了,直接替换即可,相反如果设计的时候把电脑设计成一个整体,它将很难做后期的维护,有可能一个小毛病导致整个电脑不可修复。因此单一职责使我们的产品可维护性更好、变更的风险更小,因此我们在设计接口时要尽量做到单一职责,设计颗粒度小、功能单一的类。

4 开发中的实例

接下来举个需求开发的例子,假如大学里要开发一个用户管理系统,我们将User类定义如下

public class User {private long userId;private String userName;private String nickname;private String email;private String phoneNumber;private String province; // 省private String city; // 市private String area; // 区private TeacherLevel teacherLevel; // 教师等级:讲师、副教授、教授private OfficeManager officeManager; // 办公管理者:比如网络管理、招生办管理、学籍管理等等...
}

我们先来分析下这个类,userId(用户Id)、userName(用户姓名)、nickName(昵称)这些属于用户最基本的信息,后面的email(邮箱)、phoneNumber(电话号码)、province(省)、city(市)、area(区)属于User的联系方式。

接着后面有个TeacherLevel表示教师的等级(讲师、副教授、教授等)

再往后还有个OfficeManager表示办公管理者类型(网络管理、招生办管理、学籍管理等)

我们的用户类型大致有三种分别为学生、教师、办公管理者。首先学生既不是老师也不是管理者,因此TeacherLevel、OfficeManager这两个字段对与学生来讲没有意义,同样对老师而言OfficeManager这个字段也没有意义,反之亦然。因此在这个类的设计里总有一些信息对一部分人是没有意义的,但这些信息对另一部分人又是必须的,而且这个类有以下几个问题:

  • User类庞大,在这个类包含着三种角色用户的信息
  • 任何需求方的改变都会导致User类修改,比如教师等级的修改、办公管理的修改

之所以会有这种情况就是因为类的职责不够单一,对比单一职责的定义,可以看到对于User这个"模块"它不仅仅对一类行为者负责,在单一职责的定义中我们提到"一个或多个共同需求的人我们称为行为者",在这里可以看到有三类行为者(学生、教师、办公管理者)。对于上述提到的第一种定义而言就是有多个原因导致User模块变化,而之所以有多个原因就是因为它有三种角色(学生、教师、办公管理者),那么我们是不是可以根据不同的角色进行拆分呢?来看下拆分后的结果

public class User {private long userId;private String name;private String nickname;private String email;private String phoneNumber;private String province; // 省private String city; // 市private String area; // 区.....
}
public class Teacher {private long userId;TeacherLevel teacherLevel;...
}
public class Office {private long userId;private OfficeManager officeManager;...
}

这里我们拆分出了Teacher和Office这两个类,把老师和办公人员相关的内容分别移到这两个类中,同时这两个类中都有一个userId,用来表示此角色跟哪个用户相关。这样是不是更清晰了,我们对Teacher和Office类的修改不会影响到User类。

随着业务的发展,我们开发的这个系统需要跟踪毕业之后的大学生的就业情况,比如他入职的公司、工作所在城市等,而且这样能更加方便大家的联系。那么大家毕之后会在全国各地找工作到时候所在的省、市、区是不是要经常改变,因此对于当前的User类我们还可以再拆分下

public class User {private long userId;private String name;private String nickname;private Contact contact;
}
public class Contact {private String phoneNumber;private String province; // 省private String city; // 市private String area; //区...
}

从刚刚这个例子,我们可以总结出,不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,可能都是不一样的。在某种应用场景或者当下的需求背景下,一个类的设计可能已经满足单一职责原则了,但如果换个应用场景或着在未来的某个需求背景下,可能就不满足了,需要继续拆分成粒度更细的类。除此之外,从不同的业务层面去看待同一个类的设计,对类是否职责单一,也会有不同的认识。比如第一次拆分之后的User类中的信息都属于用户,满足单一职责。但是如果我们从更加细分的"用户联系方式"这个更细颗粒度的业务层面来看,User类还可以划分。在真实的软件开发中,类里的方法是归为同一类功能,还是归为不相关的两个类功能,并不是那么容易判定的。

​ 所以单一职责原则受很多因素的影响,纯理论来讲这个原则是非常优秀的,但是如何精确对类进行划分其实不简单,这需要我们对当前和未来系统的发展充分了解,即便如此随着项目的发展,可能有些类逐渐不满足单一职责,我们需要重新考量对其进行划分

5 类的职责是否越单一越好

答案是否定的,我们把单一职责原则推演至极致,一个类应该只有一个方法,这样它受的影响是最小的。但是在真实的项目开发中,一个类通常都不止一个方法,如果拆分的太细也有可能影响项目的可维护性等。

6 总结

(1)单一职责原则定义:一个模块应该对一类且仅对一类行为者负责。
(2)单一职责的好处:

  • 类的复杂性降低,实现什么职责都有清晰明确的定义
  • 可读性提高
  • 可维护性提高
  • 变更引起的风险降低

(3)单一职责原则一定要结合具体背景,伴随着项目的迭代

(4)单一职责原则并非职责越单一越好,单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、低耦合。但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。

记住从今天开始将设计原则和设计模式融入到你的需求开发中

如果大家有疑问或者发现错误,欢迎在下方留言,我会在第一时间回答!!
如果觉得文章对你有帮助就帮忙点个赞吧。

转载请注明出处:https://blog.csdn.net/dmk877/article/details/143447010

参考书籍:
《架构整洁之道》
《设计模式之禅》

相关文章:

SOLID原则-单一职责原则

转载请注明出处:https://blog.csdn.net/dmk877/article/details/143447010 作为一名资深程序员越来越感觉到基础知识的重要性,比如设计原则、设计模式、算法等,这些知识的长期积累会让你突破瓶颈实现质的飞跃。鉴于此我决定写一系列与此相关的博客&…...

Transformer究竟是什么?预训练又指什么?BERT

目录 Transformer究竟是什么? 预训练又指什么? BERT的影响力 Transformer究竟是什么? Transformer是一种基于自注意力机制(Self-Attention Mechanism)的神经网络架构,它最初是为解决机器翻译等序列到序列(Seq2Seq)任务而设计的。与传统的循环神经网络(RNN)或卷…...

Jdbc批处理功能和MybatisPlus

文章目录 1. 序言2. JDBC批处理功能和rewriteBatchedStatements3. JDBC批量插入的测试4. MybatisPlus#ServiceImpl.saveBatch()5. 结语:如果对大家有帮助,请点赞支持。如果有问题随时在评论中指出,感谢。 1. 序言 MybatisPlus的ServiceImpl类…...

对于相对速度的重新理解

狭义相对论速度合成公式如下, 现在让我们尝试用另一种方式把它推导出来。 我们先看速度的定义, 常规的速度合成方式如下, 如果我们用速度的倒数来理解速度, 原来的两个相对速度合成, 是因为假定了时间单位是一样的&am…...

Scala的属性访问权限(一)默认访问权限

//eg:银行账户存钱取钱 // 账户类: // -balance() 余额 // -deposit() 存钱 // -withdraw() 取钱 // -transfer(to:账户,amount:Dobule)转账 package Test1104 //银行账户class BankAccount(private var balance:Int){def showMoney():Unit {println(s"…...

【算法】(Python)贪心算法

贪心算法: 又称贪婪算法,greedy algorithm。贪心地追求局部最优解,即每一步当前状态下最优选择。试图通过各局部最优解达到最终全局最优解。但不从整体最优上考虑,不一定全局最优解。步骤:从初始状态拆分成一步一步的…...

条件logistic回归原理及案例分析

前面介绍的二元、多分类、有序Logistic回归都属于非条件Logistic回归,每个个案均是相互独立关系。在实际研究中,还有另外一种情况,即个案间存在配对关系,比如医学研究中配对设计的病例对照研究,此时违反了个案相互独立…...

redis7学习笔记

文章目录 1. 简介1.1 功能介绍1.1.1 分布式缓存1.1.2 内存存储和持久化(RDBAOF)1.1.3 高可用架构搭配1.1.4 缓存穿透、击穿、雪崩1.1.5 分布式锁1.1.6 队列 1.2 数据类型StringListHashSetZSetGEOHyperLogLogBitmapBitfieldStream 2. 命令2.1 通用命令copydeldumpexistsexpire …...

重学Android:自定义View基础(一)

前言 作为一名安卓开发,也被称为大前端,做一个美观的界面,是我们必备的基础技能,可能在开发中我们最常用的是系统自带的View,因为他能满足绝大部分需求,难一点的我们也可以上Github上找个三方库使用&#…...

前端好用的网站分享——CSS(持续更新中)

1.CSS Scan 点击进入CSS Scan CSS盒子阴影大全 2.渐变背景 点击进入color.oulu 3.CSS简化压缩 点击进入toptal 4.CSS可视化 点击进入CSS可视化 这个强推,话不多说,看图! 5.Marko 点击进入Marko 有很多按钮样式 6.getwaves 点击进入getwaves 生…...

华为HarmonyOS借助AR引擎帮助应用实现虚拟与现实交互的能力3-获取设备位姿

设备位姿描述了物体在真实世界中的位置和朝向。AR Engine提供了世界坐标下6自由度(6DoF)的位姿计算,包括物体的位置(沿x、y、z轴方向位移)和朝向(绕x、y、z轴旋转)。通过AR Engine,您…...

qt QColorDialog详解

1、概述 QColorDialog是Qt框架中的一个对话框类,专门用于让用户选择颜色。它提供了一个标准的颜色选择界面,其中包括基本的颜色选择器(如调色板和颜色轮)、自定义颜色输入区域以及预定义颜色列表。QColorDialog支持RGB、HSV和十六…...

【测试小白--如何写好测试用例--测试用例编写的方法+结合常见登录模块为实例--保姆级教学】

测试用例编写方法&登录模块实例 一、测试用例编写方法1. 等价类划分2. 边界值分析3. 状态转换测试4. 决策表测试5. 错误推测6. 用户场景测试7. 安全测试用例 二、登录模块测试用例实例1. 等价类划分2. 边界值分析3. 状态转换测试4. 决策表测试5. 错误推测6. 用户场景测试7.…...

真题--数组循环题目

1.逆序数表达数组2.用数组表示费波纳希数列3.用数组排序4.二维数组转置5.找到二维数组其中的最大数值6.输出字符数组7.字符数组输出菱形图案8.输入一行字符&#xff0c;统计有多少单词9.有三个字符串&#xff0c;找到最大字符串 1.逆序数表达数组 #include<stdio.h> int…...

【Linux系列】在Linux下安装微信

文章目录 前言一、通用Linux系统使用Flatpak安装&#xff08;推荐&#xff09;1. 安装flatpak2. 安装微信 二、国产Linux 前言 此前&#xff0c;微信的Linux版一直在内测阶段&#xff0c;只有在国产的Linux系统和Debian系系统上可以正常安装&#xff0c;如果有心细的好伙伴应该…...

还在使用ElementUI不如试一试DaisyUI,DaisyUI: Tailwind CSS 的高效组件库,

DaisyUI: Tailwind CSS 的高效组件库 daisyUI官网&#xff1a;https://daisyui.com/ 在现代网页开发中&#xff0c;快速构建美观且响应式的用户界面是每个开发者追求的目标。Tailwind CSS 是一个流行的实用程序优先的 CSS 框架&#xff0c;它允许开发者直接在 HTML 中使用预…...

高光谱激光雷达遥感团队成员白杰博士获全国激光雷达优博论文奖

\quad \quad 2024年11月1日—4日&#xff0c;第八届全国激光雷达大会在桂林理工大学大学召开。本届大会&#xff0c;国际数字地球学会中国国家委员会激光雷达专业委员会组织了本年度优秀博士学位论文评选&#xff0c;经初评、函评、投票和公示&#xff0c;最终评选出了全国激光…...

24年配置CUDA12.4,Pytorch2.5.1,CUDAnn9.5运行环境

没什么好介绍的&#xff0c;直接说了。 下载 首先打开命令行&#xff0c;输入代码查看显卡最高支持的cuda版本&#xff0c;下载的版本不要高于该版本 nvidia-smi PyTorch 插件这个是PyTorch下载地址&#xff0c;就按照我这么选CUDA版本就选最新的&#xff0c;看好绿框里的CU…...

基于springboot得高校评教教师工作量管理系统设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…...

Rust 力扣 - 1456. 定长子串中元音的最大数目

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们遍历长度为k的窗口&#xff0c;我们只需要记录窗口内的元音字母数量即可&#xff0c;遍历过程中刷新最大数目 题解代码 impl Solution {pub fn max_vowels(s: String, k: i32) -> i32 {let s s.as_byt…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

QMC5883L的驱动

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

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...