单一职责原则(Single Responsibility Principle,SRP)(中)

简介: 单一职责原则(Single Responsibility Principle,SRP)(中)

2.3 用户管理

用户、机构、角色管理这些模块,基本上使用的都是RBAC模型(Role-Based Access Control,基于角色的访问控制,通过分配和取消角色来完成用户权限的授予和取消,使动作主体(用户)与资源的行为(权限)分离),这确实是一个很好的解决办法。

对于用户管理、修改用户的信息、增加机构(一个人属于多个机构)、增加角色等,用户有这么多的信息和行为要维护,我们就把这些写到一个接口中,都是用户管理类:

  • 用户信息维护类图
  • 1.png
  • 很有问题,用户属性和用户行为严重耦合!这个接口确实设计得一团糟:
  • 应该把用户信息抽取成一个BO(Business Object,业务对象)
  • 把行为抽取成一个Biz(Business Logic,业务逻辑)
  • 职责划分后的类图
  • 2.png
  • 重新拆分成两个接口:


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类图
  • image.png
  • 以上我们把一个接口拆分成两个接口的动作,就是依赖了单一职责原则,那什么是单一职责原则呢?单一职责原则的定义是:应该有且仅有一个原因引起类的变更。

2.4 电话通话

电话通话的时候有4个过程发生:拨号、通话、回应、挂机。

那我们写一个接口

image.png

电话过程

image.png

这个接口有问题吗?是的,这个接口接近于完美。

单一职责原则要求一个接口或类只有一个原因引起变化,即一个接口或类只有一个职责,它就负责一件事情,看看上面的接口:


只负责一件事情吗?

是只有一个原因引起变化吗?

好像不是!IPhone这个接口可不是只有一个职责,它包含了两个职责:


协议管理

dial()和hangup()两个方法实现的是协议管理,分别负责拨号接通和挂机

数据传送

chat()实现的是数据的传送,把我们说的话转换成模拟信号或数字信号传递到对方,然后再把对方传递过来的信号还原成我们听得懂的语言。

协议管理的变化会引起这个接口或实现类的变化吗?

会的!

那数据传送(电话不仅可以通话,还可以上网!)的变化会引起这个接口或实现类的变化吗?

会的!


这里有两个原因都引起了类变化。这两个职责会相互影响吗?


电话拨号,我只要能接通就成,不管是电信的还是联通的协议

电话连接后还关心传递的是什么数据吗?

经过分析,我们发现类图上的IPhone接口包含了两个职责,而且这两个职责的变化不相互影响,那就考虑拆分成两个接口:


职责分明的电话类图

image.png

完全满足了单一职责原则要求,每个接口职责分明,结构清晰,但相信你在设计时肯定不会采用这种方式。一个 Phone类要把ConnectionManager和DataTransfer组合在一块才能使用。组合是一种强耦合关系,共同生命周期,这样强耦合不如使用接口实现,而且还增加了类的复杂性,多了俩类。

那就再修改一下类图:


简洁清晰、职责分明的电话类图

image.png

一个类实现了两个接口,把两个职责融合在一个类中。

你可能会说Phone有两个原因引起变化了呀!

是的,但是别忘了我们是面向接口编程,我们对外公布的是接口而非实现类。而且,若真要实现类的单一职责,还就必须使用组合模式了,这会引起类间耦合过重、类的数量增加等问题,人为地增加了设计复杂性。


好处

  • 类的复杂性降低,实现什么职责都有清晰明确的定义
  • 可读性提高,复杂性降低,那当然可读性提高了
  • 可维护性提高,可读性提高,那当然更容易维护了
  • 变更引起的风险降低,变更是必不可少的。若接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响。这对系统的扩展性、维护性都有非常大帮助。

单一职责原则最难划分的就是职责。

一个职责一个接口,但问题是“职责”没有一个量化的标准,一个类到底要负责那些职责?这些职责该怎么细化?细化后是否都要有一个接口或类?

这些都需要从实际的项目去考虑,从功能上来说,定义一个IPhone接口也没有错,实现了电话的功能,而且设计还很简单,仅仅一个接口一个实现类,实际的项目我想大家都会这么设计。项目要考虑可变因素和不可变因素,以及相关的收益成本比率,因此设计一个IPhone接口也可能是没有错的。


但若纯从“学究”理论上分析就有问题了,有两个可以变化的原因放到了一个接口中,这就为以后的变化带来了风险。如果以后模拟电话升级到数字电话,我们提供的接口IPhone是不是要修改了?接口修改对其他的Invoker类是不是有很大影响?


单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。


目录
相关文章
|
Web App开发 移动开发 Linux
DP读书:《openEuler操作系统》(七)FSCK与VFS虚拟文件系统
DP读书:《openEuler操作系统》(七)FSCK与VFS虚拟文件系统
429 0
|
存储 SpringCloudAlibaba Cloud Native
【微服务33】分布式事务Seata源码解析一:在IDEA中启动Seata Server
【微服务33】分布式事务Seata源码解析一:在IDEA中启动Seata Server
1607 0
【微服务33】分布式事务Seata源码解析一:在IDEA中启动Seata Server
|
2月前
|
缓存 Rust BI
《排查Bug的逆向思维:6个真实案例教你看透问题本质》
本文分享了6个跨技术栈开发中的真实复杂Bug案例,涉及Python/Django定时任务失效、Go分布式文件存储数据损坏、Vue 3/Vite路由切换状态异常、Flutter iOS列表白屏、.NET Core支付签名验证失败、Rust实时数据服务内存泄漏等场景。每个案例均围绕“隐性Bug”的排查过程展开,从分析异常现象入手,最终定位到技术栈底层特性、环境配置冲突、资源调度疏漏等核心症结,并给出针对性解决方案。文章还提炼出重视异常信号、全局审视系统、回归技术本质等排查原则,为开发者应对跨技术栈复杂问题提供了实战参考。
|
4月前
|
Docker 容器 持续交付
如何快速搭建 ERPNext Demo 演示?
ERPNext Demo 是一个预设数据的轻量化系统,帮助用户快速体验其核心功能。本文介绍四种快速搭建方法:Docker容器部署、自动化工具、云平台一键部署及源码定制化部署,适用于展示、培训、远程演示等场景,助力高效传递系统价值。
如何快速搭建 ERPNext Demo 演示?
|
8月前
|
机器学习/深度学习 人工智能 运维
AI 实时流量分析:运维老司机的“天眼”系统
AI 实时流量分析:运维老司机的“天眼”系统
335 14
|
算法 Linux 调度
深入探索安卓系统的多任务处理机制
【10月更文挑战第21天】 本文旨在为读者提供一个关于Android系统多任务处理机制的全面解析。我们将从Android操作系统的核心架构出发,探讨其如何管理多个应用程序的同时运行,包括进程调度、内存管理和电量优化等方面。通过深入分析,本文揭示了Android在处理多任务时所面临的挑战以及它如何通过创新的解决方案来提高用户体验和设备性能。
685 1
|
程序员 Go 区块链
GitHub 用户福利,符合条件可领取约 1500 元现金
Starknet 空投,GitHub 用户可以试下,有资格的可以领取代币,最终可提现 1500 元左右。
219 0
|
存储 运维 安全
服务器数据恢复—异常断电导致RAID5阵列信息丢失的数据恢复案例
服务器数据恢复环境: 某品牌ProLiant DL380系列服务器,服务器中有一组由6块SAS硬盘组建的RAID5阵列,WINDOWS SERVER操作系统,作为企业内部文件服务器使用。 服务器故障: 机房供电几次意外中断,服务器出现故障前最后一次异常断电重启后RAID报错,提示无法找到存储设备,进入RAID管理模块做任何操作都死机,重启服务器后问题依旧,用户联系北亚企安数据恢复中心寻求帮助。
|
数据采集 缓存 监控
优化 Grafana 性能:技巧与窍门
【8月更文第29天】Grafana 是一个非常受欢迎的开源数据可视化平台,它能够连接到各种数据源并提供高度定制化的仪表板。然而,随着数据量的增长和复杂查询的增多,Grafana 的性能可能会受到影响。本文将探讨如何优化 Grafana 的性能,以提高其响应速度和稳定性,并通过具体的代码示例来展示这些技巧。
1634 1
|
Java 测试技术 API
SpringBoot单元测试快速写法问题之计算测试用例的分支覆盖率如何解决
SpringBoot单元测试快速写法问题之计算测试用例的分支覆盖率如何解决