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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/57730.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

「C/C++」C/C++ 之 指针详解

✨博客主页何曾参静谧的博客📌文章专栏「C/C」C/C程序设计📚全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

CSS--导航栏案例

利用CSS制作北大官网导航栏 详细代码如下&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style>*{margin: 0;padding: 0;}#menu{background-color: darkred;width: 100%;height: 50px…

【语义分割|代码解析】CMTFNet-2: CNN and Multiscale Transformer Fusion Network 用于遥感图像分割!

【语义分割|代码解析】CMTFNet-2: CNN and Multiscale Transformer Fusion Network 用于遥感图像分割&#xff01; 【语义分割|代码解析】CMTFNet-2: CNN and Multiscale Transformer Fusion Network 用于遥感图像分割&#xff01; 文章目录 【语义分割|代码解析】CMTFNet-2: …

基于 Python 的 Django 框架开发的电影推荐系统

项目简介&#xff1a;本项目是基于 Python 的 Django 框架开发的电影推荐系统&#xff0c;主要功能包括&#xff1a; 电影信息爬取&#xff1a;获取并更新电影数据。数据展示&#xff1a;提供电影数据的列表展示。推荐系统&#xff1a;基于协同过滤算法实现个性化推荐。用户系…

Spring Boot 集成 RocketMQ

在现代分布式系统中&#xff0c;消息队列扮演着至关重要的角色。它能够实现系统间的异步通信、解耦组件以及提高系统的可扩展性和可靠性。RocketMQ 作为一款高性能、分布式的消息中间件&#xff0c;被广泛应用于各种大规模系统中。而 Spring Boot 作为一种流行的 Java 开发框架…

Window系统性能调优

1. 系统设置优化 性能选项调整&#xff1a; 右键点击“此电脑” > “属性” > “高级系统设置” > “性能” > “设置”。在“视觉效果”选项卡中&#xff0c;选择“调整为最佳性能”以禁用不必要的动画和效果&#xff0c;或选择自定义禁用特定效果。 电源选项&…

高并发场景下的性能测试方法!

在现代互联网应用中&#xff0c;高并发场景下的性能测试显得尤为重要。无论是电商平台的秒杀活动&#xff0c;还是社交应用的突发流量&#xff0c;都需要确保系统能够在高并发情况下稳定运行。本文将详细介绍高并发场景下的性能测试方法&#xff0c;并提供具体的方案和实战演练…

苹果开发 IOS 证书生成步骤

前提条件 你手上有一台 Macbook你的苹果账号已被添加到开发人员中 证书创建步骤 打开 XCode 直接生成 p12证书生成后&#xff0c;就可在苹果开发者管理界面中看到你的证书记录登录苹果开发中心&#xff0c;创建 profiles 文件&#xff0c;并下载以上步骤即可&#xff0c;就这…

超萌!HTMLCSS:超萌卡通熊猫头

效果演示 创建了一个卡通风格的熊猫头 HTML <div class"box"><div class"head"><div class"head-copy"></div><div class"ears-left"></div><div class"ears-right"></di…

【Spark中创建RDD的两种方式】Spark中如何获取sc对象、以及创建RDD的两种方式

文章目录 一、Spark如何获取sc对象1、windons 本地模式获取sc对象2、linux 集群模式获取sc对象 二、创建RDD的两种方式1、并行化一个已存在的集合2、读取外部共享存储系统 一、Spark如何获取sc对象 不论是本地测试还是集群模式&#xff0c;都需要指定 JAVA_HOME 和 HADOOP_HOM…

RHCE第四天笔记

1.web服务器简介 &#xff08;1&#xff09;什么是www www是world wide web的缩写&#xff0c;也就是全球信息广播的意思。通常说的上网就是使用www来查询用户 所需要的信息。www可以结合文字、图形、影像以及声音等多媒体&#xff0c;并通过可以让鼠标单击超链接的方 式将信息…

springboot高校运动会管理系统-计算机毕业设计源码33814

摘要 本文旨在介绍基于Spring Boot框架和HTML技术开发的高校运动会管理系统。通过该系统&#xff0c;学校能够更高效地组织和管理校园内的各项体育赛事&#xff0c;提升运动会的组织效率和参与体验。系统整合了Spring Boot的强大功能和HTML的灵活性&#xff0c;为高校运动会管理…

Linux特种文件系统--tmpfs文件系统

tmpfs类似于RamDisk&#xff08;只能使用物理内存&#xff09;&#xff0c;使用虚拟内存&#xff08;简称VM&#xff09;子系统的页面存储文件。tmpfs完全依赖VM&#xff0c;遵循子系统的整体调度策略。说白了tmpfs跟普通进程差不多&#xff0c;使用的都是某种形式的虚拟内存&a…

森利威尔SL2516D 耐压60V内置5V功率MOS 支持PWM LED恒流驱动器芯片

一、基本特性 型号&#xff1a;SL2516D封装&#xff1a;ESOP8工作频率&#xff1a;140kHz驱动MOS管&#xff1a;内置 二、电气特性 输入电压范围&#xff1a;8V~100V&#xff08;注意&#xff0c;虽然问题中提到耐压60V&#xff0c;但根据官方信息&#xff0c;其实际耐压范围…

力扣287.寻找重复数

1.哈希表法 #include<stdio.h> #include<stdlib.h> int func(int *arr,int len) {int *hash(int *)malloc(sizeof(int)*len);for(int i0;i<len;i){if(hash[arr[i]]1){free(hash);return arr[i];}hash[arr[i]]1;}free(hash);return -1; }int main() {int arr[5]{…

服务器数据恢复—DELL EqualLogic PS6100系列存储简介及如何收集故障信息?

DELL EqualLogic PS6100系列存储采用虚拟ISCSI SAN阵列&#xff0c;支持VMware、Solaris、Linux、Mac、HP-UX、AIX操作系统&#xff0c;提供全套企业级数据保护和管理功能&#xff0c;具有可扩展性和容错功能。DELL EqualLogic PS6100系列存储介绍&#xff1a; 1、上层应用基础…

点云学习笔记3——读取点云文件、进行统计滤波/直通滤波后可视化

一、统计滤波 #include <iostream> #include <pcl/point_cloud.h> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/filters/voxel_grid.h> #include <pcl/common/common_headers.h> #include <pcl/visualiza…

【笔面试常见题:三门问题】用条件概率、全概率和贝叶斯推导

1. 问题介绍 三门问题&#xff0c;又叫蒙提霍尔问题&#xff08;Monty Hall problem&#xff09;&#xff0c;以下是蒙提霍尔问题的一个著名的叙述&#xff0c;来自Craig F. Whitaker于1990年寄给《展示杂志》&#xff08;Parade Magazine&#xff09;玛丽莲沃斯莎凡特&#x…

C++ | Leetcode C++题解之第526题优美的排列

题目&#xff1a; 题解&#xff1a; class Solution { public:int countArrangement(int n) {vector<int> f(1 << n);f[0] 1;for (int mask 1; mask < (1 << n); mask) {int num __builtin_popcount(mask);for (int i 0; i < n; i) {if (mask &am…

windows临时安装solr

下载地址 https://dlcdn.apache.org/lucene/solr/8.11.4/solr-8.11.4.zip jdk1.8 解压&#xff0c;进入bin目录&#xff0c;打开cmd&#xff0c;执行这个命令就启动好了 .\solr.cmd start PS D:\xxxxx\solr-8.11.4\bin> .\solr.cmd start Java HotSpot(TM) 64-Bit Serv…