设计模式六大原则例子(二)-- 单一职责原则(SRP)例子

之前我们对设计模式的六大原则做了简单归纳,这篇博客是对单一职责原则进行的举例说明。

单一职责原则的意义

对象不应该承担太多职责,正如人不应该一心分为二用。唯有专注,才能保证对象的高内聚;唯有单一,才能保证对象的细粒度。对象的高内聚与细粒度有利于对象的重用。一个庞大的对象承担了太多的职责,当客户端需要该对象的某一个职责时,就不得不将所有的职责都包含进来,从而造成冗余代码或代码的浪费。这实际上保证了DRY原则,即”不要重复你自己(Don’t Repeat Yourself)”,确保系统中的每项知识或功能都只在一个地方描述或实现。

单一职责原则并不是极端地要求我们只能为对象定义一个职责,而是利用极端的表述方式重点强调:
1. 在定义对象职责时,必须考虑职责与对象之间的所属关系。
2. 职责必须恰如其分地表现对象的行为,而不至于破坏和谐与平衡的美感,甚至格格不入。换言之,该原则描述的单一职责指的是公开在外的与该对象紧密相关的一组职责。

单一职责原则的优点

单一职责原则还有利于对象的稳定(所谓”职责”,就是对象能够承担的责任,并以某种行为方式来执行)。对象的职责总是要提供给其他对象调用,从而形成对象与对象的协作,由此产生对象之间的依赖关系。对象的职责越少,则对象之间的依赖关系就越少。耦合度减弱,受其他对象的约束与牵制就越少,从而保证了系统的可扩展性。

  • 类的复杂性降低,实现什么职责都有清晰明确的定义
  • 可读性提高,复杂性降低,那当然可读性提高了
  • 可维护性提高,那当然了,可读性提高,那当然更容易维护了
  • 变更引起的风险降低,变更是必不可少的,接口的单一职责做的好的话,一个接口修改只对相应的实现类有影响

单一职责与接口隔离的区别:

  1. 单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离。
  2. 单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节; 而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。

单一职责原则的例子

1.行为与行为分离开来:
举个电话的例子,我们使用电话的时候有4个过程发生:拔号、说话、听别人说、挂机,我们将这四个功能写在一个接口里,其类图如下:
这里写图片描述
代码如下:
这里写图片描述

iPhone这些接口包含了两个职责:一个是协议管理,由dial()与 hangup()两个方法实现;一个是数据传输,由chat()与 answer()实现。其中数据传输部分chat()和 answer()会使用到协议管理的方法。

1.协议接通的变化(dial()与 hangup())会引起这个接口或实现类的变化吗?  会的!  
数据传输(电话不仅仅通话,还可以上网)的变化也会引起其变化!这两个原因都引起了类的变化。
2.电话拔通还用管使用什么协议吗?电话连接后还需要关心传递什么数据吗? 都不会,即这两个职责的变化不相互影响,那就考虑拆分成两个接口,类图如下:

这里写图片描述

虽然这个类图略有些复杂,一个手机类需要把两个对象组合在一起才能用,但是因为“数据传输”需要用到“链接管理”的功能。所以这些牺牲是合适的(推荐使用)。如果我们用接口隔离的原则去实现:会有如下的结果,弊端是会将链接管理的操作和数据传输全部耦合到iPhone中去(iPhone中会写很多的业务逻辑代码去进行我们对 链接管理+数据传输 的操作),不推荐
这里写图片描述

2.职责分明到接口:
当我们要修改用户的信息的时候:对应用户的各种信息我们可以选择每个方法对单一属性进行修改,而不是在一个接口里更改所有的信息:
这里写图片描述
职责不清晰,不单一,我们改成:
这里写图片描述

3.一个违反SRP的例子:
所谓职责扩散,就是因为某种原因,职责P被分化为粒度更细的职责P1和P2

比职:类T只负责一个职责(或功能)P,这样设计是符合SRP的。后来由于某种原因,需要将职责P细分为粒度更细的P1与P2,这时如果要遵循SRP,需要将类T也分解为两个类T1和T2,分别负责P1、P2这两个职责。但是在程序已经写好的情况下,这样做简直太费时间了。所以,简单的修改类T,用它来负责两个职责是一个比较不错的选择,虽然这样做有悖于SRP。

如,用一个类描述动物呼吸这个场景:
这里写图片描述
运行结果:
这里写图片描述

程序上线后,发现问题了,并不所有动物都呼吸空气的,如鱼是呼吸水的。 修改时如若遵循SRP,则需将Animal类细分为陆生动物类Terrestrial ,水生动物 Aquatic,代码如下:[修改方式一]
这里写图片描述


我们发现这样修改花销是很大的,除了将原来的类分解之外,还需要修改客户端。而直接修改类Animal来达成目的,虽然违背SRP,但花销却小的多,修改了原有代码,直接违反了SRP,不可这样,代码如下:[修改方式二]
这里写图片描述
运行结果是:
这里写图片描述

可以看到,这种修改方式要简单得多。但是”小金鱼呼吸空气“显然是错误的。隐患:有一天需要将鱼分为呼吸淡水和海水的鱼,则又要修改Animal类的breathe方法,而对原有的代码修改会对调用“羊”等相关功能带来风险,也许某一天,你会发现程序运行的结果变为“羊呼吸水”了。这种修改方式直接在代码级别上违背了SRP,虽然修改起来最简单,但隐患却最大。还有一种修改方式,在类级别违反了SRP,但是在方法级别遵守了SRP:[修改方式三]
这里写图片描述
运行结果:
这里写图片描述

可以看出,这种修改没有改动原来的方法,而是在类中添加了一个方法,这样虽然也违背了SRP,但在方法级别上却是符合SRP的,因为它并没有改动原来方法的代码。这三种方式各有优缺点,那么在实际编程中,采用哪一种呢?这需要根据实际情况而定:建议:

A.只有逻辑足够简单,才可以在代码级别上违背SRP;

B.只有类中方法数量足够少,才可以在方法级别上违背SRP;

例如本文所举的这个例子,它太简单,它只有一个方法,所以,无论在代码还是在方法级别违背SRP,都不会造成太大的影响。
实际应用中的类都要复杂的多,一旦发生职责扩散而需要修改类时,除非这个类本身非常简单,否则还是要遵循SRP。

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值