简介
单一职责原则(Single Responsibility Principle,SRP)又称单一功能原则,由罗伯特·C.马丁(Robert C. Martin)于《敏捷软件开发:原则、模式和实践》一书中提出的。
这里的职责是指类变化的原因,单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分(There should never be more than one reason for a class to change)。
要点
单一职责原则的描述对象
单一职责原则描述的对象包含两个,一个是类(class),一个是模块(module)。
关于这两个概念,有两种理解方式。
- 把模块看作比类更加抽象的概念,类也可以看作模块。
- 把模块看作比类更加粗粒度的代码块,模块中包含多个类,多个类组成一个模块。
单一职责原则的定义
一个类只负责完成一个职责或者功能。
也就是说,不要设计大而全的类,要设计粒度小、功能单一的类。
换个角度来讲就是,一个类包含了两个或者两个以上业务不相干的功能,那我们就说它职责不够单一,应该将它拆分成多个功能更加单一、粒度更细的类。
问题
为什么要遵守单一职责原则?
降低类的复杂度:一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多。
提高类的可读性:复杂性降低,自然其可读性会提高。
提高系统的可维护性:可读性提高,那自然更容易维护了。
变更引起的风险降低:变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可以显著降低对其他功能的影响。
如何判断类的职责是否足够单一?
不同的应用场景、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果,一些侧面的判断指标更具有指导意义和可执行性。
不满足单一职责原则的类设计
出现下面这些情况就有可能说明这类的设计不满足单一职责原则:
- 类中的代码行数、函数或者属性过多。
- 类依赖的其他类过多,或者依赖类的其他类过多。
- 私有方法过多;比较难给类起一个合适的名字。
- 类中大量的方法都是集中操作类中的某几个属性。
判断对象是多职责还是单职责
从Object Design: Roles, Responsibilies, and Collaborations这本书中提出可以从以下几方面判断出一个对象的多个行为构造出的是多职责还是单职责:
- Information holder - 该对象设计为存储对象并提供对象信息给其他对象
- Structurer - 该对象设计为维护对象与信息间的关系
- Service provider - 该对象设计为处理任务与提供服务给其他对象
- Controller - 该对象设计为负责控制一系列职责的任务处理
- Coordinator - 该对象设计为把任务绑定/委托到其他对象上
- Interfacer - 该对象设计为在各个对象间负责转化信息或者请求
类的职责是否设计得越单一越好?
单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。
同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、低耦合。
但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。
示例
如何判断类的职责是否足够单一?
在一个社交产品中,我们用下面的 UserInfo 类来记录用户的信息。你觉得,UserInfo 类的设计是否满足单一职责原则呢?
public class UserInfo {
private long userId;
private String username;
private String email;
private String telephone;
private long createTime;
private long lastLoginTime;
private String avatarUrl;
private String provinceOfAddress; // 省
private String cityOfAddress; // 市
private String regionOfAddress; // 区
private String detailedAddress; // 详细地址
}
观点
对于这个问题,有两种不同的观点:
一种观点是,UserInfo 类包含的都是跟用户相关的信息,所有的属性和方法都隶属于用户这样一个业务模型,满足单一职责原则。
另一种观点是,地址信息在 UserInfo 类中,所占的比重比较高,可以继续拆分成独立的 UserAddress 类,UserInfo 只保留除 Address 之外的其他信息,拆分之后的两个类的职责更加单一。
讲解
要从中做出选择,我们不能脱离具体的应用场景。
如果在这个社交产品中,用户的地址信息跟其他信息一样,只是单纯地用来展示,那 UserInfo 现在的设计就是合理的。
但是,如果这个社交产品发展得比较好,之后又在产品中添加了电商的模块,用户的地址信息还会用在电商物流中,那我们最好将地址信息从 UserInfo 中拆分出来,独立成用户物流信息(或者叫地址信息、收货信息等)。
结论
综上所述,评价一个类的职责是否足够单一,我们并没有一个非常明确的、可以量化的标准,可以说,这是件非常主观、仁者见仁智者见智的事情。
实际上,在真正的软件开发中,我们也没必要过于未雨绸缪,过度设计。
所以,我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。
这就是所谓的持续重构。
小知识
来自设计模式之美的用户blacknhole的回答
- 内聚和耦合其实是对一个意思(即合在一块)从相反方向的两种阐述。
- 内聚是从功能相关来谈,主张高内聚。把功能高度相关的内容不必要地分离开,就降低了内聚性,成了低内聚。
- 耦合是从功能无关来谈,主张低耦合。把功能明显无关的内容随意地结合起来,就增加了耦合性,成了高耦合。
参考资料
王争-设计模式之美
https://time.geekbang.org/column/intro/100039001
简书-木白no1-单一职责原则的理解与实现
https://www.jianshu.com/p/8876f34b0a67