设计模式:对于单一职责原则,如何判定某个类的职责足够“单一”

如何理解单一职责原则

单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。这个原则的英文描述是这样的:A class or module should have a single reponsibility。如果我们把它翻译成中文,那就是:一个类或者模块只负责完成一个职责(或者功能)

注意,这个原则描述的对象包含两个,一个是类(class),一个是模块(module)。关于模块,可以这样理解:

  • 把模块看作比类更加抽象的概念,类也可以看作模块
  • 把模块看作比类更加粗粒度的代码块,模块中包含多个类,多个类组成一个模块

下面以类为例子,来理解这个设计原则

单一职责就是一个类只负责完成一个职责或者功能。也就是说,不要设计大而全的类,要设计粒度小、功能单一的类。换个角度来讲就是,一个类包含了两个或者两个以上的业务不相干的功能,那我们就说它职责不够单一,应该将它拆分成多个功能更加单一、粒度更细的类。

如何判断类的职责足够单一

大部分情况下,类里的方法是归为同一类功能,还是归为不相关的两类功能,并不是那么容易判定的。

举个例子:在一个社交产品中,我们用下面的 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现在的设计就是合理的。但是,如果这个社交产品发展得足够好,之后又在产品中添加了电商得模块,用户的地址信息还会用在电商物流中,那我们最好将地址信息从UserInfo中拆分出来,独立成用户物流信息(或者叫地址信息、收货信息等)。

  • 我们再进一步延伸一下。如果做这个社交产品的公司发展得越来越好,公司内部又开发出了跟多其他产品(可以理解为其他 App)。公司希望支持统一账号系统,也就是用户一个账号可以在公司内部的所有产品中登录。这个时候,我们就需要继续对 UserInfo 进行拆分,将跟身份认证相关的信息(比如,email、telephone 等)抽取成独立的类。

  • 从这个例子,我们可以总结出,不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,可能都是不一样的。在某种应用场景或者当下的需求背景下,一个类的设计可能已经满足单一职责原则了,但如果换个应用场景或着在未来的某个需求背景下,可能就不满足了,需要继续拆分成粒度更细的类。

  • 除此之外,从不同的业务层面去看待同一个类的设计,对类是否职责单一,也会有不同的认识。比如,例子中的 UserInfo 类。如果我们从“用户”这个业务层面来看,UserInfo 包含的信息都属于用户,满足职责单一原则。如果我们从更加细分的“用户展示信息”“地址信息”“登录认证信息”等等这些更细粒度的业务层面来看,那 UserInfo 就应该继续拆分。

综上所述,评价一个类的职责是否足够单一,我们并没有一个非常明确的、可以量化的标准,可以说,这是件非常主观、仁者见仁智者见智的事情。实际上,在真正的软件开发中,我们也没必要过于未雨绸缪,过度设计。所以,我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构

那,这个原则如此含糊不清、模棱两可,到底该如何拿捏才好啊?判断原则如下:

  • 类中的代码行数、函数或者属性过多,会影响代码的可读性和可维护性,我们就需要考虑对类进行拆分
  • 类依赖的其他类过多,或者依赖类的其他类过多,不符合高内聚、低耦合的设计思想,我们就需要考虑对类进行拆分
  • 私有方法过多,我们就要考虑能否将私有方法独立到新的类中,设置为public方法,共更多的类使用,从而提高代码的复用性
  • 比较难给类起一个合适的名字,很难用一个业务名称概况,或者只能用一些笼统的Manager、Context之类的词语来命名,这就说明类的职责定义不够清晰
  • 类中大量的方法都是集中操作类的某几个属性,比如,在UserInfo例子中,如果一半的方法都是在操作address信息,那就可以考虑将这几个属性和对应的方法拆分出来。

问题是,多少行代码才算是行数过多呢?多少个函数、属性才称得上过多呢?

实际上,这个问题并不好定量地回答,就像你问大厨“放盐少许”中的“少许”是多少,大厨也很难告诉你一个特别具体的量值。

类的职责是否设计得越单一越好?

为了满足单一职责原则,是不是将类拆的越细越好呢?当然不是。不管是应用设计原则还是设计模式,最终的目的还是提高代码的可读性、可扩展性、复用性、可维护性等。我们在考虑应用某一个设计原则是否合理的时候,也可以以此作为最终的考量标准

总结

(1)如何理解单一职责原则(SRP)?

  • 一个类只负责完成一个职责或者功能
  • 不要设计大而全的类,要设计粒度小、功能单一的类
  • 单一职责原则是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性

(2)如何判断类的职责是否足够单一

不同的应用场景、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。实际上,一些侧面的判断指标更具有指导意义和可执行性,比如,出现下面这些情况就有可能说明这类的设计不满足单一职责原则:

  • 类中的代码行数、函数或者属性过多;
  • 类依赖的其他类过多,或者依赖类的其他类过多;
  • 私有方法过多;
  • 比较难给类起一个合适的名字;
  • 类中大量的方法都是集中操作类中的某几个属性。

(3)类的职责是否设计得越单一越好?

  • 单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性
  • 同时,类职责单一,类依赖的和被依赖的其他类也会减少,减少了代码的耦合性,以此来实现代码的高内聚、低耦合
  • 但是,如果拆分得够细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值