2019独角兽企业重金招聘Python工程师标准>>>

1.定义

单一职责原则概念::规定一个类应该只有一个发生变化的原因。

There should never be more than one reason for a class to change.

2.单一职责理解

从单一职责原则概念,我们将职责定义为一个变化的因素,如可这个类有多个变化因素,这个类就违背了单一职责原则。

在设计类的时候需要将不同的职责分离到单独的类中。一个类只需要专一实现自身的职责,Do one thing and do it well。专注能保证对象的高内聚和细粒度,有利于对象的重用。

3.问题和解决方案:

例1

类T负责两个不同的职责:职责P1、职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原来运行的职责P2功能发生故障。解决方法:分别建立两个类完成对应的功能。

只要做过项目,肯定要接触到用户、机构、角色管理这些模块,基本上使用的都是RBAC模型,确实是很好的一个解决办法。我们今天要讲的是用户管理、修改用户的信息、增加机构(一个人属于多个机构)、增加角色等,用户有这么的信息和行为要维护,我们就把这些写到一个接口中,都是用户管理类嘛,我们先来看它的类图:

太Easy的类图了,    这个接口设计得有问题,用户的属性(Property)和用户的行为(Behavior)没有分开,这是一个严重的错误!非常正确,这个接口确实设计得一团糟,应该把用户的信息抽取成一个BO(Bussiness Object,业务对象),把行为抽取成一个BIZ(Business Logic,业务逻辑),按照这个思路对类图进行修正,如下图:

重新拆封成两个接口,IUserBO负责用户的属性,简单地说,IUserBO的职责就是收集和反馈用户的属性信息;IUserBiz负责用户的行为,完成用户信息的维护和变更。各位可能要说了,这个与我实际工作中用到的User类还是有差别的呀!别着急,我们先来看一看分拆成两个接口怎么使用。OK,我们现在是面向接口编程,所以产生了这个UserInfo对象之后,当然可以把它当IUserBO接口使用。当然,也可以当IUserBiz接口使用,这要看你在什么地方使用了。要获得用户信息,就当是IUserBO的实现类;要是希望维护用户的信息,就把它当作IUserBiz的实现类就成了,代码清单1-1所示。

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

确实可以如此,问题也解决了,但是我们来回想一下我们刚才的动作,为什么要把一个接口拆分成两个呢?其实,在实际的使用中,我们更倾向于使用两个不同的类或接口:一个是IUserBO, 一个是IUserBiz,类图应该如下图所示。

上图就是项目中常常使用的SPR(There should never be more than one reason for a class to change)类图;

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

实现类也比较简单,我就不再写了,大家看看这个接口有没有问题?我相信大部分的读者都会说这个没有问题呀,以前我就是这么做的呀,某某书上也是这么写的呀,还有什么什么的源码也是这么写的!是的,这个接口接近于完美,看清楚了,是“接近”!单一职责原则要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一件事情,看看上面的接口只负责一件事情吗?是只有一个原因引起变化吗?好像不是!

IPhone这个接口可不是只有一个职责,它包含了两个职责:一个是协议管理,一个是数据传送。diag()和huangup()两个方法实现的是协议管理,分别负责拨号接通和挂机;chat()和answer()是数据的传送,把我们说的话转换成模拟信号或数字信号传递到对方,然后再把对方传递过来的信号还原成我们听得懂语言。我们可以这样考虑这个问题,协议接通的变化会引起这个接口或实现类的变化吗?会的!那数据传送(想想看,电话不仅仅可以通话,还可以上网)的变化会引起这个接口或实现类的变化吗?会的!那就很简单了,这里有两个原因都引起了类的变化,而且这两个职责会相互影响吗?电话拨号,我只要能接通就成,甭管是电信的还是网通的协议;电话连接后还关心传递的是什么数据吗?不关心,你要是乐意使用56K的小猫传递一个高清的片子,那也没有问题(顶多有人说你13了)。通过这样的分析,我们发现类图上的IPhone接口包含了两个职责,而且这两个职责的变化不相互影响,那就考虑拆开成两个接口,其类图如下图所示。

这个类图看着有点复杂了,完全满足了单一职责原则的要求,每个接口职责分明,结构清晰,但是我相信你在设计的时候肯定不会采用这种方式,一个手机类要把ConnectionManager和DataTransfer组合在一块才能使用。组合是一种强耦合关系,你和我都有共同的生命期,这样的强耦合关系还不如使用接口实现的方式呢,而且还增加了类的复杂性,多了两个类。经过这样的思考后,我们再修改一下类图,如下图所示。

简洁清晰、职责分明的电话类图

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

看过电话这个例子后,是不是有点反思了,我以前的设计是不是有点的问题了?不,不是的,不要怀疑自己的技术能力,单一职责原则最难划分的就是职责。一个职责一个接口,但问题是“职责”是一个没有量化的标准,一个类到底要负责那些职责?这些职责该怎么细化?细化后是否都要有一个接口或类?这些都需要从实际的项目去考虑,从功能上来说,定义一个IPhone接口也没有错,实现了电话的功能,而且设计还很简单,仅仅一个接口一个实现类,实际的项目我想大家都会这么设计。项目要考虑可变因素和不可变因素,以及相关的收益成本比率,因此设计一个IPhone接口也可能是没有错的。但是,如果纯从“学究”理论上分析就有问题了,有两个可以变化的原因放到了一个接口中,这就为以后的变化带来了风险。如果以后模拟电话升级到数字电话,我们提供的接口IPhone是不是要修改了?接口修改对其他的Invoker类是不是有很大影响?!

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

例2

再比如:生产手机

假定现在有如下场景:国际手机运营商那里定义了生产手机必须要实现的接口,接口里面定义了一些手机的属性和行为,手机生产商如果要生产手机,必须要实现这些接口。

我们首先以手机作为单一职责去设计接口,方案如下。

  /// <summary>/// 充电电源类/// </summary>public class ElectricSource{    }
public interface IMobilePhone{//运行内存string RAM { get; set; }//手机存储内存string ROM { get; set; }//CPU主频string CPU { get; set; }//屏幕大小int Size { get; set; }//手机充电接口void Charging(ElectricSource oElectricsource);//打电话void RingUp();//接电话void ReceiveUp();//上网void SurfInternet();}

然后我们的手机生产商去实现这些接口

//具体的手机示例public class MobilePhone:IMobilePhone{public string RAM{get {throw new NotImplementedException();}set{ throw new NotImplementedException();}}public string ROM{get{throw new NotImplementedException();}set{ throw new NotImplementedException();}}public string CPU{get{ throw new NotImplementedException();}set{ throw new NotImplementedException();}}public int Size{get{throw new NotImplementedException();}set{throw new NotImplementedException();}}public void Charging(ElectricSource oElectricsource){throw new NotImplementedException();}public void RingUp(){throw new NotImplementedException();}public void ReceiveUp(){throw new NotImplementedException();}public void SurfInternet(){throw new NotImplementedException();}}

这种设计有没有问题呢?这是一个很有争议的话题。单一职责原则要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一件事情,原则上来说,我们以手机作为单一职责去设计,也是有一定的道理的,因为我们接口里面都是定义的手机相关属性和行为,引起接口变化的原因只可能是手机的属性或者行为发生变化,从这方面考虑,这种设计是有它的合理性的,如果你能保证需求不会变化或者变化的可能性比较小,那么这种设计就是合理的。但实际情况我们知道,现代科技日新月异,科技的进步促使着人们不断在手机原有基础上增加新的属性和功能。比如有一天,我们给手机增加了摄像头,那么需要新增一个像素的属性,我们的接口和实现就得改吧,又有一天,我们增加移动办公的功能,那么我们的接口实现是不是也得改。由于上面的设计没有细化到一定的粒度,导致任何一个细小的改动都会引起从上到下的变化,有一种“牵一发而动全身”的感觉。所以需要细化粒度,下面来看看我们如何变更设计。

二次设计 变更:

我们将接口细化

 //手机属性接口public interface IMobilePhoneProperty{//运行内存string RAM { get; set; }//手机存储内存string ROM { get; set; }//CPU主频string CPU { get; set; }//屏幕大小int Size { get; set; }//摄像头像素string Pixel { get; set; }}//手机功能接口public interface IMobilePhoneFunction{//手机充电接口void Charging(ElectricSource oElectricsource);//打电话void RingUp();//接电话void ReceiveUp();//上网void SurfInternet();//移动办公void MobileOA();}

实现类

 //手机属性实现类public class MobileProperty:IMobilePhoneProperty{public string RAM{get{ throw new NotImplementedException();}set{ throw new NotImplementedException();}}public string ROM{get{ throw new NotImplementedException();}set{ throw new NotImplementedException();}}public string CPU{get{ throw new NotImplementedException();}set{throw new NotImplementedException();}}public int Size{get{throw new NotImplementedException();}set{throw new NotImplementedException();}}public string Pixel{get{throw new NotImplementedException();}set{throw new NotImplementedException();}}}//手机功能实现类public class MobileFunction:IMobilePhoneFunction{public void Charging(ElectricSource oElectricsource){throw new NotImplementedException();}public void RingUp(){throw new NotImplementedException();}public void ReceiveUp(){throw new NotImplementedException();}public void SurfInternet(){throw new NotImplementedException();}public void MobileOA(){throw new NotImplementedException();}}//具体的手机实例public class HuaweiMobile{private IMobilePhoneProperty m_Property;private IMobilePhoneFunction m_Func;public HuaweiMobile(IMobilePhoneProperty oProperty, IMobilePhoneFunction oFunc){m_Property = oProperty;m_Func = oFunc;}}

对于上面题的问题,这种设计能够比较方便的解决,如果是增加属性,只需要修改IMobilePhoneProperty和MobileProperty即可;如果是增加功能,只需要修改IMobilePhoneFunction和MobileFunction即可。貌似完胜第一种解决方案。那么是否这种解决方案就完美了呢?答案还是看情况。原则上,我们将手机的属性和功能分开了,使得职责更加明确,所有的属性都由IMobilePhoneProperty接口负责,所有的功能都由IMobilePhoneFunction接口负责,如果是需求的粒度仅仅到了属性和功能这一级,这种设计确实是比较好的。反之,如果粒度再细小一些呢,那我们这种职责划分是否完美呢?比如我们普通的老人机只需要一些最基础的功能,比如它只需要充电、打电话、接电话的功能,但是按照上面的设计,它也要实现IMobilePhoneFunction接口,某一天,我们增加了一个新的功能玩游戏,那么我们就需要在接口上面增加一个方法PlayGame()。可是我们老人机根本用不着实现这个功能,可是由于它实现了该接口,它的内部实现也得重新去写。从这点来说,以上的设计还是存在它的问题。那么,我们如何继续细化接口粒度呢?

最终设计

接口细化粒度设计如下

//手机基础属性接口public interface IMobilePhoneBaseProperty{//运行内存string RAM { get; set; }//手机存储内存string ROM { get; set; }//CPU主频string CPU { get; set; }//屏幕大小int Size { get; set; }}//手机扩展属性接口public interface IMobilePhoneExtentionProperty{//摄像头像素string Pixel { get; set; }}//手机基础功能接口public interface IMobilePhoneBaseFunc{//手机充电接口void Charging(ElectricSource oElectricsource);//打电话void RingUp();//接电话void ReceiveUp();}//手机扩展功能接口public interface IMobilePhoneExtentionFunc{//上网void SurfInternet();//移动办公void MobileOA();//玩游戏void PlayGame();}

实现类和上面类似

//手机基础属性实现public class MobilePhoneBaseProperty : IMobilePhoneBaseProperty{public string RAM{get{throw new NotImplementedException();}set{throw new NotImplementedException();}}public string ROM{get{throw new NotImplementedException();}set {throw new NotImplementedException();}}public string CPU{get{throw new NotImplementedException();}set{ throw new NotImplementedException();}}public int Size{get{ throw new NotImplementedException();}set{ throw new NotImplementedException();}}}//手机扩展属性实现public class MobilePhoneExtentionProperty : IMobilePhoneExtentionProperty{public string Pixel{get{ throw new NotImplementedException();}set{ throw new NotImplementedException();}}}//手机基础功能实现public class MobilePhoneBaseFunc : IMobilePhoneBaseFunc{public void Charging(ElectricSource oElectricsource){throw new NotImplementedException();}public void RingUp(){throw new NotImplementedException();}public void ReceiveUp(){throw new NotImplementedException();}}//手机扩展功能实现public class MobilePhoneExtentionFunc : IMobilePhoneExtentionFunc{public void SurfInternet(){throw new NotImplementedException();}public void MobileOA(){throw new NotImplementedException();}public void PlayGame(){throw new NotImplementedException();}}

此种设计能解决上述问题,细分到此粒度,这种方案基本算比较完善了。能不能算完美?这个得另说。接口的粒度要设计到哪一步,取决于需求的变更程度,或者说取决于需求的复杂度

因为每一个职责都是变化的中心。当需求变更时,这个变化将通过更改职责相关的类来实现。如果一个类拥有多个职责,那么这个类在变更的时候可能会影响到其他的类,产生无法预期的破坏。所以单一职责原则有利于对象的稳定,让多个对象负责各自的职责,然后对象之间进行协作要比一个对象负责多个职责强的多,方法之间也是这样。

4.优点:

类的复杂性降低,实现什么职责都有清晰明确的定义;

可读性提高,复杂性降低,那当然可读性提高了;

可维护性提高,那当然了,可读性提高,那当然更容易维护了;

变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大帮助。

5.难点职责的划分

类的设计尽量做到只有一个原因引起变化。单一职责原则是一个非常简单的原则,但通常也是最难做的正确的一个原则。职责的联合是在实践中经常碰到的事情。理论是理论,实践是实践。要考虑相关因素和收益等。

6.总结 :

以上通过一个应用场景简单介绍了下单一职责原则的使用,类的设计尽量做到只有一个原因引起变化。上面三种设计,没有最合理,只有最合适。理解单一职责原则,最重要的就是理解职责的划分,职责划分的粒度取决于需求的粒度,没有最好的设计,只有最适合的设计。

Do one thing,and do it well~.

参考链接:

https://zhuanlan.zhihu.com/p/24198903

https://www.cnblogs.com/cbf4life/archive/2009/12/11/1622166.html

转载于:https://my.oschina.net/u/3701483/blog/1577552

七种设计原则(二)单一职责原则相关推荐

  1. 设计模式六大原则(一)----单一职责原则

    设计模式六大原则之[单一职则原则] 一.什么是单一职责原则 首先, 我们来看单一职责的定义. 单一职责原则,全称Single Responsibility Principle, 简称SRP. A cl ...

  2. 面向对象的七种原则:单一职责原则,开放关闭原则

    我们的知识星球马上就要开始更新设计模式了,在更新设计模式之前,我们是不是需要做一些准备呢?否则设计模式中一些遵循的原则大家会一头雾水,所以我今天来给大家说一些面向对象的七种原则,有人说是6种有人说是7 ...

  3. Java设计原则之单一职责原则、开闭原则、里氏代换原则

    文章目录 面向对象设计原则概述 单一职责原则 开闭原则 里氏代换原则 面向对象设计原则概述 软件的可维护性(Maintainability)和可复用性(Reusability)是两个非常重要的用于衡量 ...

  4. 经典设计原则:单一职责原则(SRP)

    本文详解设计原则中的单一职责原则,目的还是提高代码的可读性.可扩展性.复用性.可维护性等. 目录 1. 单一职责原则(SRP) 2. 如何理解单一职责原则? 3. 如何判断类的职责是否足够单一? 4. ...

  5. 设计原则:单一职责原则

    单一职责原则(SRP) 单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP.这个原则的英文描述是这样的:A class or module shoul ...

  6. SOLID原则:单一职责原则(SRP)

    SOLID:SOLID 原则并非单纯的1个原则,而是由5个设计原则组成,它们分别是:单一职责原则.开闭原则.里式替换原则.接口隔离原则和依赖反转原则,SOLID 由5个设计原则的头一个字母组成. 如何 ...

  7. 接口隔离原则和单一职责原则区别

    接口隔离原则和单一职责原则区别 单一职责原则是备受争议的原则,根据不同的业务逻辑,它会将系统功能模块划分成不同种类,产生多样的接口,同时每个接口尽量只包含一个功能(方法). 而产生争议的原因就是这个业 ...

  8. 设计原则之单一职责原则

    定义:一个类只负责一项职责,应该只有一个能引起它变化的原因. 问题:类T负责两个不同的职责:职责P1,职责P2.当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常 的职责P2功能发生 ...

  9. 架构中的设计原则之单一职责原则 - 《java开发技术-在架构中体验设计模式和算法之美》...

    2019独角兽企业重金招聘Python工程师标准>>> 单一职责模式: 单一职责原则的核心思想就是:系统中的每一个对象都应该只有一个单独的职责,而所有对象所关注的就是自身职责的完成. ...

  10. 六大设计原则之单一职责原则(SRG)

    在做代码练习或者开发的过程中,我们会发现自己写的类越来越大,该类的功能也越来越多.有一些开发者包括之前的我看到自己写的类够大,功能够多是往往会充满自豪感.但是当某个功能需要做一个小改动时,就会发现整个 ...

最新文章

  1. Mysql的日志那些事
  2. hdu4884 模拟
  3. Java Vector与ArrayList的区别
  4. Oracle数据类型与.NET中的对应关系
  5. stm32开发问题集锦
  6. 将GDB中的输出定向到文件
  7. 牛客 —— 湖南大学第十六届程序设计竞赛(重现赛)
  8. java 视频转码工具类_JavaCV入门指南:FrameConverter转换工具类及CanvasFrame图像预览工具类(javaCV教程完结篇)...
  9. 2022,从阅读开始
  10. 靠写代码登上胡润富豪榜花半年写得Python基础 入门必看
  11. vue异步获取图片流文件进行图片显示
  12. 微信在线接口调试工具的使用
  13. 【转载】发一篇能激励大家拼搏的文章,文中内容属实
  14. python做表格教程_表格函数教程
  15. 【转】计算机词汇简繁体对照表
  16. 计算机专英语第6版第八章翻译,计算机专业英语第八章课文翻译.doc
  17. php 反射 thinkphp,PHP反射(ReflectionClass、ReflectionMethod)在ThinkPHP框架的控制器调度模块中的应用...
  18. excel 数据匹配、数据对应、建立数据映射关系(设置A对应1、B对应2)
  19. python递归函数特点_Python递归函数特点及原理解析
  20. 动易开源了,是不是说动易也免费了?

热门文章

  1. java读取TXT文件的方法
  2. mkdir,mkdirs区别
  3. 命令python所在的驱动器和文件夹_Python读取不同本地驱动器位置的文件
  4. python字符串处理函数汇总_Python函数汇总
  5. 高频hf调制方式_收藏!AM的三种调制电路
  6. hibernate java内存一次能取多少条_Hibernate管理Session和批量操作分析
  7. php 浏览器 兼容,兼容ie6浏览器的php下载文件代码分享
  8. 谷歌Android无障碍套件,谷歌为无障碍套件添加盲文键盘:无需额外硬件就能打字...
  9. free网页服务器,Web网站服务(一)
  10. chrome插件 vscode_2020年,前端开发者必备的10个VS Code扩展插件