大旗不挥,谁敢冲锋—六大设计原则之单一职责原则

2024-02-04 21:32

本文主要是介绍大旗不挥,谁敢冲锋—六大设计原则之单一职责原则,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

单一职责原则

 

1. 我是“牛”类,我可以担任多职吗

单一职责原则,英文名称是:Single Responsibility Principle,简称SRP。该职责备受争议,争议之处在于——对职责的定义,什么是类的职责,以及怎么划分类的职责。但是首先弄清楚什么是单一职责原则?

在做项目的时,用户、机构、角色管理这些模块肯定会接触到,基本上使用的都是RBAC模型(Role-Based Access Control,基于角色的访问控制,通过分配和取消角色来完成用户权限的授予和取消,使动作主体【用户】与资源的行为【权限】分离)

对于用户管理、修改用户的信息、增加机构(一个人属于多个机构)、增加角色等,用户有这么多的信息和行为要维护,于是将这些写到一个接口中,反正都是用户管理类嘛,看它的类图,如图1-1所示:

图 1-1 用户信息维护类图

 

如上图所示,显然这个接口设计得有问题——用户的属性和行为没有分开,这是一个严重的错误!正确的做法应该是把用户的信息抽取成一个BO(Business Object,业务对象),把行为抽取成一个Biz(Business Logic,业务逻辑),按照这个思路对类图进行修正,如图1-2所示:

图 1-2 职责拆分后的类图

 

拆分成两个接口,IUserBO负责用户属性(收集和反馈用户的属性信息);IUserBiz负责用户的行为(完成用户信息的维护和变更)。采用面向接口编程,所以产生了UserInfo对象之后,当然可以把它当作IUseBO接口使用。也可以当作IUserBiz接口使用,这就决定于具体的业务场景。如果希望获得用户信息,就当是IUserBO的实现类;要是希望维护用户的信息,就把它当作IUserBiz的实现类,例如代码1-1所示:

代码清单1-1 分清职责后的代码实例

......
IUserInfo userInfo = new IUserInfo();
// 我要赋值了,我就认为它是纯粹的BO
IUserBO userBO = (IUserBo)userInfo;
userBO.setPassword("123456");
// 我要执行一个动作了,我就认为是一个业务逻辑类
IUserBiz userBiz = (IuserBiz)userInfo;
userBiz.deleteUser();
......

这样问题解决了,但是经过分析刚才的动作,为什么要将一个接口拆分成两个呢?其实,在实际使用中,更倾向于使用两个不同的类或接口;一个是IUserBO,一个是IUserBiz,leu如如图1-3所示:

图1-3 项目中经常采用的SRP类图

 

上面将接口拆分成两个接口的动作,就是依赖了单一职责原则,那什么是单一职责原则呢?单一职责原则定义是:应该有且仅有一个原因引起类的变更。

2. 绝技,打破传统思维

SRP的原话解释:There should never be more than one reason for a class to change

再举一个电话的例子,再电话通话的时候有4个过程发生:拨号、通话、回应、挂机,写一个接口,其类图如1-4所示:

图 1-4 电话类图

 

代码如清单1-2所示:

public interface IPhone {// 接通电话void dial(String phoneNumber);// 通话void chat(Object o);// 通话完毕,接电话void hangup();
}

上面代码清单中的IPhone接口,接近“完美”!单一职责原则要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责

,它就负责一件事情,但是上面的接口只负责一件事情吗?是只有一个原因引起变化吗?好像不是!

IPhone这个接口可不是只有一个职责,它包含了两个职责:一个协议管理,一个是数据传送。dial()和hangup()两个方法实现的是协议管理,分别负责拨号接通和挂机;chat()实现的是数据的传送,把说的话转换成模拟信号或数字信号传递到对方,然后再把对方传递过来的信号还原成我们听得懂的语言。

协议接通的变化会引起这个接口或实现类的变化吗?会的!那数据传送(电话不仅仅可以通话,还可以上网)的变化会引起这个接口或实现类的变化吗?也会的!因此,这两个原因都引起了累的变化!。这两个职责会相互影响吗?电话拨号,只要能够接通就成,甭管是电信的还是网通的协议;电话连接后还关心传递的是什么数据吗?通过这样的分析,我们发现类图上的IPhone接口包含了两个职责,并且这两个职责的变化互不影响,那就考虑拆分成两个接口,其类图如图1-5所示:

图 1-5 职责分明的电话类图

 

这个类图看上去有点复杂,但完全满足了单一职责原则的要求,每个接口职责分明,结构清晰,但是在设计的时候肯定不会采用这种方式

。一个手机类要把ConnectionManager和DataTransfer组合在一块才能使用。组合是一种强耦合关系,你和我都有共同的生命周期,这样的强耦合关系还不如使用接口实现的方式呢?而且还增加了类的复杂性,多了两个类。经过这样的考虑,修改了一下类图,如图1-6所示:

图1-6 简介清晰、职责分明的电话类图

 

 

这样的设计才是完美的,一个类实现了两个接口,把两个职责融合在一个类中。不过会发现其中Phone有两个原因引起变化了呀,是的,但由于采用的是面向接口编程,对外公布的是接口而不是实现类。而且,如果真要实现类的单一职责,这个就必须使用上面的组合模式了,这会引起类间的耦合过重、类的数量增加等问题,人为的增加了设计的复杂性。这样会得不偿失!

通过上面的例子,可以总结一下单一职责原则的好处:

  • 类的复杂性降低。实现什么职责都有清晰明确的定义
  • 可读性提高。复杂性降低,当然可读性提高了
  • 可维护性提高。可读性提高,当然更容易维护了
  • 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做的很好,一个接口修改只对相应的实现类有影响,对其他接口无影响,这对系统的扩展性、维护性都有非常大的帮助。

虽然单一职责原则有上面的好处,但是单一职责的“职责”怎么划分。一个职责一个接口,问题是“职责”没有一个量化的标准,一个类到底要负责哪些职责?这些职责该怎么细化?细化后是否都要一个接口或类?这些都需要从实际项目去考虑,从功能上说,定义一个IPhone接口也没有错,实现了电话的功能,并且设计简单,仅仅一个接口和一个实现类,在实际项目中也会这样设计。项目要考虑可变因素和不可变因素,以及相关的收益成本比率,因此设计一个IPhone接口也可能是没有错的。但是,如果纯从“学究”理论上分析就有问题了,有两个可以变化的原因放到了一个接口中,这就为以后的变化带来了风险。如果电话升级到了数字电话,我们提供的接口IPhone是不是要修改了?接口修改对其他的Invoker类是不是有很大的影响?

注意:单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计的是否优良,但是“职责”和“变化原因”都是不可以度量的,因项目而异,因环境而异。

3. 我单纯,我快乐

对于接口,在设计的时候一定要做到单一,但是对于实现类就需要多方面的考虑了。生搬硬套单一职责原则会引起类的剧增,给维护带来非常多的麻烦,而且过分细分类的职责也会人为地增加系统的复杂性。本来一个类可以实现的行为硬要拆成两个类,然后在使用聚合或组合的方式耦合在一起,人为制造了系统的复杂性。所以原则是死的,人是活的。

单一职责原则很难在项目中得到体现,非常难。原因是,在国内,技术人员的地位和话语权比较低(深有体会),因此在项目中需要考虑环境、考虑工作量、考虑人员的技术水平,考虑硬件的资源情况,等等,最终妥协的结果是常常违背单一职责原则。然而,我坚信随着技术的深入,单一职责必然会深入到项目设计中,而且这个原则是那么简单!

单一职责适用于接口、类,同时也使用于方法。就是,一个方法尽可能做一件事情,比如一个方法修改用户密码,不要把这个方法放到“修改用户信息”方法中,这个方法的颗粒度很粗,比如图1-7中所示的方法。

图 1-7 一个方法承担多个职责

 

 

在IUserManager中定义了一个方法changeUser,根据传递的类型不同,把可变长度参数changeOptions修改到了userBO这个对象上,并调用持久层的方法保存到数据库中。这种方法书写的非常糟糕,原因是:方法职责不清晰,不单一,不要让别人猜测这个方法可能是用来处理什么逻辑,比较好的设计如图1-8所示:

图 1-8 一个方法承担一个职责

 

 

通过类图可以知道,如果修改用户名称,就调用changeUserName方法;要修改家庭地址,就调用changeHomeAddress方法;要修改单位电话,就调用changeOfficeTel。每个方法的职责非常清晰明确,不仅有利于开发,也为日后的维护带来遍历。要逐渐养成这样的习惯。

所以,如果对接口、类和方法使用了单一职责原则,会带来很大的便利的。

4. 最佳实践

类的单一职责原则在运用中受非常多因素的制约,纯理论来讲,这个原则非常的优秀,但现实有现实的难处,必须去考虑项目工期、成本、人员技术水平、硬件情况、网络情况甚至有时候还需要政府政策、垄断协议等因素。

总之,对于单一职责原则,接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。

 

这篇关于大旗不挥,谁敢冲锋—六大设计原则之单一职责原则的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

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

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

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

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

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

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

SprinBoot+Vue网络商城海鲜市场的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质创作者,全网30w+

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

单片机毕业设计基于单片机的智能门禁系统的设计与实现

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

Spring的设计⽬标——《Spring技术内幕》

读《Spring技术内幕》第二版,计文柯著。 如果我们要简要地描述Spring的设计⽬标,可以这么说,Spring为开发者提供的是⼀个⼀站式的轻量级应⽤开发框架(平台)。 作为平台,Spring抽象了我们在 许多应⽤开发中遇到的共性问题;同时,作为⼀个轻量级的应⽤开发框架,Spring和传统的J2EE开发相⽐,有其⾃⾝的特点。 通过这些⾃⾝的特点,Spring充分体现了它的设计理念:在

开题报告中的研究方法设计:AI能帮你做什么?

AIPaperGPT,论文写作神器~ https://www.aipapergpt.com/ 大家都准备开题报告了吗?研究方法部分是不是已经让你头疼到抓狂? 别急,这可是大多数人都会遇到的难题!尤其是研究方法设计这一块,选定性还是定量,怎么搞才能符合老师的要求? 每次到这儿,头脑一片空白。 好消息是,现在AI工具火得一塌糊涂,比如ChatGPT,居然能帮你在研究方法这块儿上出点主意。是不

创业者该如何设计公司的股权架构

本文来自七八点联合IT橘子和车库咖啡的一系列关于设计公司股权结构的讲座。 主讲人何德文: 在公司发展的不同阶段,创业者都会面临公司股权架构设计问题: 1.合伙人合伙创业第一天,就会面临股权架构设计问题(合伙人股权设计); 2.公司早期要引入天使资金,会面临股权架构设计问题(天使融资); 3.公司有三五十号人,要激励中层管理与重要技术人员和公司长期走下去,会面临股权架构设计问题(员工股权激

PHP最长单一子串

<?php//方法一$s='abcccddddddcdefg';$max='';while($s!=''){$i=0; while($i<strlen($s) && $s[$i]==$s[0]) $i++;if ($i>strlen($max)){$max=substr($s,0,$i);} $s=substr($s,$i);}echo $m