1.单一职责原则,(Single Responsibility Principle).
定义:There should never be more than one reason for a class to change,应该有且仅有一个原因引起类的变更。
职责:业务逻辑,或者对象能够承担的责任,并以某种行为方式来执行。
2.理解
该原则提出了对对象职责的一种理想期望。对象不应该承担太多职责,正如人不应该一心分为二用。唯有专注,才能保证对象的高内聚;唯有单一,才能保证对象的细粒度。对象的高内聚与细粒度有利于对象的重用。
一个庞大的对象承担了太多的职责,当客户端需要该对象的某一个职责时,就不得不将所有的职责都包含进来,从而造成冗余代码或代码的浪费。这实际上保证了DRY原则,即"不要重复你自己(Don'tRepeatYourself)",确保系统中的每项知识或功能都只在一个地方描述或实现。
单一职责原则还有利于对象的稳定。所谓"职责",就是对象能够承担的责任,并以某种行为方式来执行。对象的职责总是要提供给其他对象调用,从而形成对象与对象的协作,由此产生对象之间的依赖关系。对象的职责越少,则对象之间的依赖关系就越少,耦合度减弱,受其他对象的约束与牵制就越少,从而保证了系统的可扩展性。
单一职责原则并不是极端地要求我们只能为对象定义一个职责,而是利用极端的表述方式重点强调,在定义对象职责时,必须考虑职责与对象之间的所属关系。职责必须恰如其分地表现对象的行为,而不至于破坏和谐与平衡的美感,甚至格格不入。换言之,该原则描述的单一职责指的是公开在外的与该对象紧密相关的一组职责。
例如,在媒体播放器中,可以在MediaPlayer类中定义一组与媒体播放相关的方法,如Open()、Play()、Stop()等。这些方法从职责的角度来讲,是内聚的,完全符合单一职责原则中"专注于做一件事"的要求。如果需求发生扩充,需要我们提供上传、下载媒体文件的功能。那么在设计时,就应该定义一个新类如MediaTransfer,由它来承担这一职责;而不是为了方便,草率地将其添加到MediaPlayer类中。
单一职责适用于接口、类、同时也适用于方法。方法的粒度也不宜过粗。
3.问题由来
类T负责两个不事的职责:职责P1、职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原来运行的职责P2功能发生故障。解决方法:分别建立两个类完成对应的功能。[待补充]
4.好处:
- 类的复杂性降低,实现什么职责都有清晰明确的定义;
- 可读性提高,复杂性降低,那当然可读性提高了;
- 可维护性提高,那当然了,可读性提高,那当然更容易维护了;
- 变更引起的风险降低,变更是必不可少的,接口的单一职责做的好的话,一个接口修改只对相应的实现类有影响,与其他的接口无影响,这个是对项目有非常大的帮助。
5.难点
5.1 职责划分无量化标准:学究理论还是工程应用?后者时,要考虑可变因素与不可变因素,以及相关的收益成本比率等。
5.2 单一职责妥协:项目中单一职责原则很少得以体现,或者非常难(囿于国内技术人员的地位、话语权、项目中的环境、工作量、人员的技术水平、硬件资源等,最终的结果就是常常违背单一职责原则)。
6.实践建议
6.1 接口一定要做到SRP,类的设计尽量做到只有一个原因引起变化。
6.2 妥协原则:
A.只有逻辑足够简单,才可以在代码级别上违背SRP;
B.只有类中方法数量足够少,才可以在方法级别上违背SRP;
C.实际应用中的类都要复杂的多,一旦发生职责扩散而需要修改类时,除非这个类本身非常简单,否则还是要遵循SRP。
7.范例
7.1 职责分明示例(Role-Based Access Control,基于角色的访问控制)(属性与行为分离)
项目中常用到 用户、机构、角色管理这些模块,基本上使用的都是RBAC模型(通过分配和取消角色来完成用户权限的授予与取消,使动作主体(用户)与资源的行为(权限)分离)。 但上述接口设计得有问题,用户的属性与用户的行为没有分开。
应将其拆分为两个接口,IUserBO负责用户的属性,也即收集和反馈用户的属性信息;IUserBiz负责用户的行为,完成用户信息的维护与变更。
- .......
- IUserBizuserInfo=newUserInfo();
- //我要赋值了,我就认为它是一个纯粹的BO
- IUserBOuserBO=(IUserBO)userInfo;
- userBO.setPassword("abc");
- //我要执行动作了,我就认为是一个业务逻辑类
- IUserBizuserBiz=(IUserBiz)userInfo;
- userBiz.deleteUser();
- .......
实际上,在项目中,更倾向于使用如下的结构图:
7.2 职责分明的电话类图(行为与行为相分离)
举个电话的例子,电话通话的时候有4个过程发生:拔号、通话、回应、挂机,写一个接口,其类图如下:
copy