定义:There should never be more than one reason for a class to change,应该有且仅有一个缘由引发类的变动。java
职责:业务逻辑,或者对象可以承担的责任,并以某种行为方式来执行。编程
该原则提出了对对象职责的一种理想指望。对象不该该承担太多职责,正如人不该该一心分为二用。惟有专一,才能保证对象的高内聚;惟有单一,才能保证对象的细粒度。对象的高内聚与细粒度有利于对象的重用。架构
高内聚,低耦合( 严于律己,宽以待人)spa
高内聚是说模块内部要高度聚合,低耦合是说模块与模块之间的藕合度要尽可能低。前者是说模块内部的关系,后者是说模块与模块间的关系。设计
粗粒度:表示类别级,即仅考虑对象的类别(the type of object),不考虑对象的某个特定实例。好比,用户管理中,建立、删除,对全部的用户都一视同仁,并不区分操做的具体对象实例。 rest
细粒度:表示实例级,即须要考虑具体对象的实例(the instance of object),固然,细粒度是在考虑粗粒度的对象类别以后才再考虑特定实例。好比,合同管理中,列表、删除,须要区分该合同实例是否为当前用户所建立。code
通常权限的设计是解决了粗粒度的问题,由于这部分具备通用性,而细粒度能够当作业务部分,由于其具备不肯定性。对象
一个庞大的对象承担了太多的职责,当客户端须要该对象的某一个职责时,就不得不将全部的职责都包含进来,从而形成冗余代码或代码的浪费。这实际上保证了DRY原则,即"不要重复你本身(Don't Repeat Yourself)",确保系统中的每项知识或功能都只在一个地方描述或实现。 接口
单一职责原则还有利于对象的稳定。所谓"职责",就是对象可以承担的责任,并以某种行为方式来执行。对象的职责老是要提供给其余对象调用,从而造成对象与对象的协做,由此产生对象之间的依赖关系。对象的职责越少,则对象之间的依赖关系就越少,耦合度减弱,受其余对象的约束与牵制就越少,从而保证了系统的可扩展性。ip
单一职责原则并非极端地要求咱们只能为对象定义一个职责,而是利用极端的表述方式重点强调,在定义对象职责时,必须考虑职责与对象之间的所属关系。职责必须恰如其分地表现对象的行为,而不至于破坏和谐与平衡的美感,甚至格格不入。换言之,该原则描述的单一职责指的是公开在外的与该对象紧密相关的一组职责。
例如,在媒体播放器中,能够在MediaPlayer类中定义一组与媒体播放相关的方法,如Open()、Play()、Stop()等。这些方法从职责的角度来说,是内聚的,彻底符合单一职责原则中"专一于作一件事"的要求。若是需求发生扩充,须要咱们提供上传、下载媒体文件的功能。那么在设计时,就应该定义一个新类如MediaTransfer,由它来承担这一职责;而不是为了方便,草率地将其添加到MediaPlayer类中。
单一职责适用于接口、类、同时也适用于方法。方法的粒度也不宜过粗。
类T负责两个不事的职责:职责P1、职责P2。当因为职责P1需求发生改变而须要修改类T时,有可能会致使原来运行的职责P2功能发生故障。解决方法:分别创建两个类完成对应的功能。
5.1 职责划分无量化标准:学究理论仍是工程应用?后者时,要考虑可变因素与不可变因素,以及相关的收益成本比率等。
5.2 单一职责妥协:项目中单一职责原则不多得以体现,或者很是难(囿[yòu]于国内技术人员的地位、话语权、项目中的环境、工做量、人员的技术水平、硬件资源等,最终的结果就是经常违背单一职责原则)。
6.1 接口必定要作到SRP,类的设计尽可能作到只有一个缘由引发变化。
6.2 妥协原则:
A.只有逻辑足够简单,才能够在代码级别上违背SRP;
B.只有类中方法数量足够少,才能够在方法级别上违背SRP;
C.实际应用中的类都要复杂的多,一旦发生职责扩散而须要修改类时,除非这个类自己很是简单,不然仍是要遵循SRP。
项目中经常使用到 用户、机构、角色管理这些模块,基本上使用的都是RBAC模型(经过分配和取消角色来完成用户权限的授予与取消,使动做主体(用户)与资源的行为(权限)分离)。 但上述接口设计得有问题,用户的属性与用户的行为没有分开。
RBAC概念
摘自百度百科:
基于角色的权限访问控制(Role-Based Access Control)做为传统访问控制(自主访问,强制访问)的有前景的代替受到普遍的关注。在RBAC中,权限与角色相关联,用户经过成为适当角色的成员而获得这些角色的权限。这就极大地简化了权限的管理。在一个组织中,角色是为了完成各类工做而创造,用户则依据它的责任和资格来被指派相应的角色,用户能够很容易地从一个角色被指派到另外一个角色。角色可依新的需求和系统的合并而赋予新的权限,而权限也可根据须要而从某角色中回收。角色与角色的关系能够创建起来以囊括更普遍的客观状况。
应将其拆分为两个接口,IUserBO负责用户的属性,也即收集和反馈用户的属性信息;IUserBiz负责用户的行为,完成用户信息的维护与变动。
代码清单1-1 分清职责后的代码示例
....... IUserBiz userInfo = new UserInfo(); //我要赋值了,我就认为它是一个纯粹的BO IUserBO userBO = (IUserBO)userInfo; userBO.setPassword("abc"); //我要执行动做了,我就认为是一个业务逻辑类 IUserBiz userBiz = (IUserBiz)userInfo; userBiz.deleteUser(); .......
实际上,在项目中,更倾向于使用以下的结构图:
举个电话的例子,电话通话的时候有4个过程发生:拔号、通话、回应、挂机,写一个接口,其类图以下:
public interface IPhone { //拨通电话 public void dial(String phoneNumber); //通话 public void chat(Object o); //回应,只有本身说话而没有回应,那算啥?! public void answer(Object o); //通话完毕,挂电话 public void huangup(); }
IPhone这个接口包含了两个职责:一个是协议管理,由dial()与 hangup()两个方法实现;一个是数据传输,由chat()与 answer()实现。
考虑:
A.协议接通的变化会引发这个接口或实现类的变化吗? 会的! 数据传输(电话不只仅通话,还能够上网)的变化也会引发其变化!这两个缘由都引发了类的变化。B.电话拔通还用管使用什么协议吗?电话链接后还须要关心传递什么数据吗?
都不,即这两个职责的变化不相互影响,那就考虑拆分红两个接口,类图以下:
这个类图略有些复杂,一个手机类须要把两个对象组合在一块儿才能用。组合是一种强耦合关系,还不如使用接口实现的方式呢。修改以下:
修改用户信息方法 ----〉 职责分明的方法
上述写法很糟(职责不清晰,不单一),改了吧。。。
分层架构模式实际上也体现了这一原则,它将整个系统按照职责的内聚性分为不一样的层,层内的模块与类具备宏观的内聚性,它们关注的事情应该是一致的。例如,领域逻辑层就主要关注系统的业务逻辑与业务流程,而数据的持久化与访问则交由数据访问层来负责。以订单的管理为例,咱们在领域逻辑层中定义以下的类OrderManager:
public class OrderManager { private IOrderRepository repository = RepositoryFactory.createOrderRepository(); public void place(Order order) { if (order.IsValid()) { repository.add(order); } else { throw new InvalidOperationException("Order can't be placed. "); } } public void cancel(Order order) { if (order.isValid() && order.canCancel(DateTime.now())) { repository.remove(order); } else { throw new InvalidOperationException("Order can't be canceled. "); } } } public static class RepositoryFactory { public static IOrderRepository createOrderRepository() { return new OrderRepository(); } }
OrderManager类的实现体现了单一职责原则的思想。
这些类的职责以及协做关系如图2-4所示。
将数据访问的逻辑从领域对象中分离出去是有道理的,由于数据访问逻辑的变化方向与订单业务逻辑的变化方向是不一致的,引发职责发生变化的缘由也不相同。这也是单一职责原则的核心思想。遵循该原则,就可以有效地分离对象的变与不变,将变化的职责以抽象的方式独立于原对象以外,原对象就更加稳定。Martin Fowler认为,设计一个模型时应使该模型中最频繁修改的部分所影响的类型数量达到最少。咱们对访问Order数据表的逻辑进行了封装与抽象,以隔离数据访问逻辑的变化,即便数据访问逻辑发生变化,它影响到的只是OrderRepository类而已。
所谓职责扩散,就是由于某种缘由,职责P被分化为粒度更细的职责P1和P2.
比职:类T只负责一个职责P,这样设计是符合SRP的。后来因为某种缘由,须要将职责P细分为粒度更细的P1与P2,这时若是要遵循SRP,须要将类T也分解为两个类T1和T2,分别负责P1、P2这两个职责。可是在程序已经写好的状况下,这样作简直太费时间了。因此,简单的修改类T,用它来负责两个职责是一个比较不错的选择,虽然这样作有悖于SRP。
如,用一个类描述动物呼吸这个场景:
class Animal{ public void breathe(String animal){ System.out.println(animal+"呼吸空气"); } } public class Client{ public static void main(String[] args){ Animal animal = new Animal(); animal.breathe("羊"); } } 运行结果: 羊呼吸空气
程序上线后,发现问题了,并不全部动物都呼吸空气的,如鱼是呼吸水的。 修改时如若遵循SRP,则需将Animal类细分为陆生动物类Terrestrial ,水生动物 Aquatic,代码以下:[修改方式一]
class Animal{ public void breathe(String animal){ if("鱼".equals(animal)){ System.out.println(animal+"呼吸水"); }else{ System.out.println(animal+"呼吸空气"); } } } public class Client{ public static void main(String[] args){ Animal animal = new Animal(); animal.breathe("羊"); animal.breathe("鱼"); } } 运行结果: 羊呼吸空气 鱼呼吸水
能够看到,这种修改方式要简单得多。可是却存在着隐患:有一天须要将鱼分为呼吸淡水和海水的鱼,则又要修改Animal类的breathe方法,而对原有的代码修改会对调用“羊”等相关功能带来风险,也许某一天,你会发现程序运行的结果变为“羊呼吸水”了。这种修改方式直接在代码级别上违背了SRP,虽然修改起来最简单,但隐患却最大。还有一种修改方式:[修改方式三]
class Animal{ public void breathe(String animal){ System.out.println(animal+"呼吸水"); } public void breathe2(String animal){ System.out.println(animal+"呼吸空气"); } } public class Client{ public static void main(String[] args){ Animal animal = new Animal(); animal.breathe("羊"); animal.breathe2("鱼"); } } 运行结果: 羊呼吸空气 鱼呼吸水
能够看出,这种修改没有改动原来的方法,而是在类中添加了一个方法,这样虽然也违背了SRP,但在方法级别上倒是符合SRP的,由于它并无改动原来方法的代码。这三种方式各有优缺点,那么在实际编程中,采用哪种呢?这须要根据实际状况而定:建议:
A.只有逻辑足够简单,才能够在代码级别上违背SRP;
B.只有类中方法数量足够少,才能够在方法级别上违背SRP;
例如本文所举的这个例子,它太简单,它只有一个方法,因此,不管在代码仍是在方法级别违背SRP,都不会形成太大的影响。实际应用中的类都要复杂的多,一旦发生职责扩散而须要修改类时,除非这个类自己很是简单,不然仍是要遵循SRP。