2.3 用户管理
用户、机构、角色管理这些模块,基本上使用的都是RBAC模型(Role-Based Access Control,基于角色的访问控制,通过分配和取消角色来完成用户权限的授予和取消,使动作主体(用户)与资源的行为(权限)分离),这确实是一个很好的解决办法。
对于用户管理、修改用户的信息、增加机构(一个人属于多个机构)、增加角色等,用户有这么多的信息和行为要维护,我们就把这些写到一个接口中,都是用户管理类:
- 用户信息维护类图
- 很有问题,用户属性和用户行为严重耦合!这个接口确实设计得一团糟:
- 应该把用户信息抽取成一个BO(Business Object,业务对象)
- 把行为抽取成一个Biz(Business Logic,业务逻辑)
- 职责划分后的类图
- 重新拆分成两个接口:
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
- 项目中经常采用的SRP类图
- 以上我们把一个接口拆分成两个接口的动作,就是依赖了单一职责原则,那什么是单一职责原则呢?单一职责原则的定义是:应该有且仅有一个原因引起类的变更。
2.4 电话通话
电话通话的时候有4个过程发生:拨号、通话、回应、挂机。
那我们写一个接口
电话过程
这个接口有问题吗?是的,这个接口接近于完美。
单一职责原则要求一个接口或类只有一个原因引起变化,即一个接口或类只有一个职责,它就负责一件事情,看看上面的接口:
只负责一件事情吗?
是只有一个原因引起变化吗?
好像不是!IPhone这个接口可不是只有一个职责,它包含了两个职责:
协议管理
dial()和hangup()两个方法实现的是协议管理,分别负责拨号接通和挂机
数据传送
chat()实现的是数据的传送,把我们说的话转换成模拟信号或数字信号传递到对方,然后再把对方传递过来的信号还原成我们听得懂的语言。
协议管理的变化会引起这个接口或实现类的变化吗?
会的!
那数据传送(电话不仅可以通话,还可以上网!)的变化会引起这个接口或实现类的变化吗?
会的!
这里有两个原因都引起了类变化。这两个职责会相互影响吗?
电话拨号,我只要能接通就成,不管是电信的还是联通的协议
电话连接后还关心传递的是什么数据吗?
经过分析,我们发现类图上的IPhone接口包含了两个职责,而且这两个职责的变化不相互影响,那就考虑拆分成两个接口:
职责分明的电话类图
完全满足了单一职责原则要求,每个接口职责分明,结构清晰,但相信你在设计时肯定不会采用这种方式。一个 Phone类要把ConnectionManager和DataTransfer组合在一块才能使用。组合是一种强耦合关系,共同生命周期,这样强耦合不如使用接口实现,而且还增加了类的复杂性,多了俩类。
那就再修改一下类图:
简洁清晰、职责分明的电话类图
一个类实现了两个接口,把两个职责融合在一个类中。
你可能会说Phone有两个原因引起变化了呀!
是的,但是别忘了我们是面向接口编程,我们对外公布的是接口而非实现类。而且,若真要实现类的单一职责,还就必须使用组合模式了,这会引起类间耦合过重、类的数量增加等问题,人为地增加了设计复杂性。
好处
- 类的复杂性降低,实现什么职责都有清晰明确的定义
- 可读性提高,复杂性降低,那当然可读性提高了
- 可维护性提高,可读性提高,那当然更容易维护了
- 变更引起的风险降低,变更是必不可少的。若接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响。这对系统的扩展性、维护性都有非常大帮助。
单一职责原则最难划分的就是职责。
一个职责一个接口,但问题是“职责”没有一个量化的标准,一个类到底要负责那些职责?这些职责该怎么细化?细化后是否都要有一个接口或类?
这些都需要从实际的项目去考虑,从功能上来说,定义一个IPhone接口也没有错,实现了电话的功能,而且设计还很简单,仅仅一个接口一个实现类,实际的项目我想大家都会这么设计。项目要考虑可变因素和不可变因素,以及相关的收益成本比率,因此设计一个IPhone接口也可能是没有错的。
但若纯从“学究”理论上分析就有问题了,有两个可以变化的原因放到了一个接口中,这就为以后的变化带来了风险。如果以后模拟电话升级到数字电话,我们提供的接口IPhone是不是要修改了?接口修改对其他的Invoker类是不是有很大影响?
单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。