单一职责原则的英文名称是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对象之后,当然既可以把它当IUserBO接口使用,也可以当IUserBiz接口使用。 要获得用户信息,就当是IUserBO的实现类;要是希望维护用户的信息,就把它当作IUserBiz 的实现类。比如:
IUserInfo userInfo = new UserInfo();
//要赋值了,把它作为一个纯粹的BO
IUserBO userBO = (IUserBO)userInfo;
userBO.setPassword("abc");
//要执行动作了,把它作为一个业务逻辑类
IUserBiz userBiz = (IUserBiz)userInfo;
userBiz.deleteUser();
为什么要把一个接口 拆分成两个呢?其实,在实际的使用中,更倾向于使用两个不同的类或接口:一个是 IUserBO,一个是IUserBiz,类图如图1-3所示:
1-3:项目中经常采用的SRP类图
以上把一个接口拆分成两个接口的动作,就是依赖了单一职责原则,那什么是单一 职责原则呢?
单一职责原则的定义是:应该有且仅有一个原因引起类的变更。
单一职责原则看起来似乎比较简单,实际上软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。在实际的业务当中去把那些职责区分出来不是那么简单。
比如,生活中常用的电话,通话的时候有4个过程发生:拨号、通话、回 应、挂机,写一个接口,其类图如图1-4所示:
1-4:电话类图
接口的代码:
public interface IPhone {
//拨通电话
public void dial(String phoneNumber);
//通话
public void chat(Object o);
//通话完毕,挂电话
public void hangup();
}
这个类看起来是没什么问题的。单一职责原则 要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一 件事情,上面的接口只负责一件事情吗?是只有一个原因引起变化吗?并不是!
IPhone这个接口包含了两个职责:一个是协议管理,一个是数传送。
dial()和hangup()两个方法实现的是协议管理,分别负责拨号接通和挂机;chat()实现 的是数据的传送,把说的话转换成模拟信号或数字信号传递到对方,然后再把对方传递 过来的信号还原成听得懂的语言。实际上,协议接通的变化会引起这个接口或实现类的变化。数据传送的变化同样也会引起这个接口或实现类的变化。那么现在这里就有两个原因都引 起了类的变化。这两个职责会相互影响吗?电话拨号,要能接通就行,不管是电信的还 是网通的协议;电话连接后还关心传递的是什么数据吗?通过这样的分析,发现类图上的IPhone接口包含了两个职责,而且这两个职责的变化不相互影响,那就考虑拆分成两个接口,其类图如图1-5所示:
1-5: 职责分明的电话类图
这个类图看上去有点复杂了,虽然完全满足了单一职责原则的要求,每个接口职责分明,但是一个手机类要把 ConnectionManager和DataTransfer组合在一块才能使用。组合是一种强耦合关系,你和我都有共同的生命期,这样的强耦合关系还不如使用接口实现的方式,而且还增加了类的复杂性,多了两个类。经过这样的思考后,再修改一下类图,如图1-6所示:
1-6:简洁清晰、职责分明的电话类图
这里一个类实现了两个接口,把两个职责融合在一个类中。似乎Phone有两个原因引起变化了,但是这是面向接口编程,对外公布的是接口而不是实现类。而且,如果真要实现类的单一职责,这个就必须使用上面的组合模式了,这会引起类间耦合过重、类的数量增加等问题,人为地增加了设计的复杂性。
单一职责原则的好处: ● 类的复杂性降低,实现什么职责都有清晰明确的定义; ● 可读性提高,复杂性降低,那当然可读性提高了; ● 可维护性提高,可读性提高,那当然更容易维护了; ● 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
当然了,追求完美也得从项目的实际功能需求出发。从功能上来说,定义一个IPhone接口也是可行的,实现了电话的功能,而且设计还很简单,仅仅一个接口一个实现类。
⇐⇐设计模式——零:设计模式概览 设计模式—— 二:里氏替换原则⇒⇒
参考: 【1】:《设计模式之禅》 【2】:《大话设计模式》 【3】:面向对象设计原则之单一职责原则