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

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

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

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

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

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

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

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

相关文章

内网横向移动小结

windows Windows-Mimikatz 适用环境&#xff1a; 微软为了防止明文密码泄露发布了补丁 KB2871997&#xff0c;关闭了 Wdigest 功能。当系统为 win10 或 2012R2 以上时&#xff0c;默认在内存缓存中禁止保存明文密码&#xff0c;此时可以通过修改注册表的方式抓取明文&#xff…

Android 静默安装成功后自启动

近期开发上线一个常驻app&#xff0c;项目已上线&#xff0c;今天随笔记录一下静默安装相关内容。我分三篇静默安装&#xff08;root版&#xff09;、静默安装&#xff08;无障碍版&#xff09;、监听系统更新、卸载、安装。 先说说我的项目需求&#xff1a;要求app一直运行&am…

总结 | vue3项目初始化(附相应链接)

如何运行 vue 项目&#xff1a;vscode运行vue项目_vscode启动vue项目命令-CSDN博客 vue3项目搭建 目录管理 git管理&#xff1a;vue3项目搭建并git管理_git 新建vue3项目-CSDN博客 目录调整&#xff1a;vue3项目 - 目录调整-CSDN博客 vscode中快速生成vue3模板&#xff1a…

基于ssm高校专业信息管理系统设计与实现论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对高校专业信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差…

3.23项目:聊天室

1、 基于UDP的网络聊天室 项目需求&#xff1a; 如果有用户登录&#xff0c;其他用户可以收到这个人的登录信息如果有人发送信息&#xff0c;其他用户可以收到这个人的群聊信息如果有人下线&#xff0c;其他用户可以收到这个人的下线信息服务器可以发送系统信息 服务器 #inc…

一、SpringBoot基础搭建

本教程主要给初学SpringBoot的开发者&#xff0c;通过idea搭建单体服务提供手把手教学例程&#xff0c;主要目的在于理解环境的搭建&#xff0c;以及maven模块之间的整合与调用 源码&#xff1a;jun/learn-springboot 以商城项目为搭建例子&#xff0c;首先计划建1个父模块&…

Wireshark TS | DNS 案例分析之外的思考

前言 承接之前一篇《Packet Challenge 之 DNS 案例分析》&#xff0c;在数据包跟踪文件 dnsing.pcapng 中&#xff0c;关于第 4 题&#xff08;What is the largest DNS response time seen in this trace file? &#xff09;的分析过程中曾经碰到一个小问题&#xff0c;主要…

看PDF时点击书签页面变小的解决方法

用Adobe acrobat看PDF时&#xff0c;多数PDF点击书签跳到的页面字体大小很合适&#xff0c;但是有些pdf文件&#xff0c;一点击书签后&#xff0c;就变为很小或者很大&#xff0c;下面是解决方法&#xff1a; ①点击某个书签&#xff0c;然后按ctrlA全选书签&#xff0c;右键—…

MySQL数据库-MySQL基础-下篇-函数、约束、多表查询、事务

文章目录 函数一、字符串函数练习 二、数值函数三、日期函数四、流程函数总结 约束概述约束演示外键约束概念语法删除/更新行为 总结 多表查询多表关系一对多&#xff08;多对一&#xff09;多对多一对一 多表查询概述内连接外连接自连接*联合查询-union, union all子查询标量子…

一文读懂IP地址

IP地址&#xff08;Internet Protocol Address&#xff09;是指互联网协议地址&#xff0c;是IP协议提供的一种统一的地址格式&#xff0c;它为互联网上的每一个网络和每一台主机分配一个逻辑地址&#xff0c;以此来屏蔽物理地址的差异。IP地址的主要特点是具有唯一性&#xff…

华为校招机试 - 循环依赖(20240320)

题目描述 给定一组元素,及其依赖关系,一个元素可以依赖于多个元素(不包括自己,被依赖元素不会重复),一个元素也可被多个元素依赖。 假定总是存在唯一的循环依赖,请输出该循环依赖。 输入描述 第一行是个正整数 N (1 < N < 100),表示依赖关系的个数。 下面每…

AbstractQueuedSynchronizer 独占式源码阅读

概述 ● 一个int成员变量 state 表示同步状态 ● 通过内置的FIFO队列来完成资源获取线程的排队工作 属性 AbstractQueuedSynchronizer属性 /*** 同步队列的头节点 */private transient volatile Node head;/*** 同步队列尾节点&#xff0c;enq 加入*/private transient …

【XR806开发板试用】使用PWM模块模拟手机呼吸灯提示功能

一般情况下&#xff0c;我们的手机在息屏状态&#xff0c;当收到消息处于未读状态时&#xff0c;会有呼吸灯提醒&#xff0c;这次有幸抽中XR806开发板的试用&#xff0c;经过九牛二虎之力终于将环境搞好了&#xff0c;中间遇到各种问题&#xff0c;在我的另一篇文章中已详细描述…

面试算法-82-不同路径

题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f; …

2403d,d语言直接利用llama.cpp

原文 无需绑定,只需制作个带单个包含的虚C文件,然后就可开始构建由llama.cpp提供支持的D应用,仅此而已,除了制作虚文件外,无需额外工作. 在窗口上使用dmdv2.107来测试. 代码 //llamad.c: #include "llama.h"从llama.cpp的简单的移植D版本示例: //llamad.d: modul…

MySQL (1)

目录 一、数据库的基本概念 1.1 数据 &#xff08;Data&#xff09; 1.2 表 1.3 数据库 2 数据库管理系统 3 数据库系统 二、数据库发展史 关于第三代数据库 三、关系型数据库 当今主流数据库介绍 关系数据库应用 四、非关系数据库介绍 五、MySQL数据库介绍 MySQL商业…

如何利用自动化和智能化技术提高仓储行业效率?

仓储行业作为物流领域的重要环节&#xff0c;其效率的提升对于整个供应链的顺畅运作至关重要。自动化和智能化技术的引入&#xff0c;为仓储行业带来了革命性的变革。 一&#xff0e;自动化技术的应用 自动化仓储系统 通过引入自动化仓储系统&#xff0c;如高架叉车、自动化…

vscode调试launch.json常用格式

目录 1、简单的模版 2、简单的案例 2.1、python 执行.py 文件 2.2、调式多个文件 2.3、torchrun、deepspeed 调试 1、简单的模版 定义一个简单的模版如下&#xff1a; {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访…

redis和rabbitmq实现延时队列

redis和rabbitmq实现延时队列 延迟队列使用场景Redis中zset实现延时队列Rabbitmq实现延迟队列 延迟队列使用场景 1. 订单超时处理 延迟队列可以用于处理订单超时问题。当用户下单后&#xff0c;将订单信息放入延迟队列&#xff0c;并设置一定的超时时间。如果在超时时间内用户…

网络编程套接字——实现简单的TCP网络程序

目录 1、TCP socket API详解 socket()&#xff1a; bind()&#xff1a; listen(): accept(): connect(): 2、简易的TCP网络程序 TcpServer.hpp TcpClient.cc Main.cc Log.hpp ThreadPool.hpp Task.hpp Init.hpp Daemon.hpp dict.txt Makefile 1、TCP socket A…