SOLID原则:单一职责原则(SRP)

SOLID:SOLID 原则并非单纯的1个原则,而是由5个设计原则组成,它们分别是:单一职责原则、开闭原则、里式替换原则、接口隔离原则和依赖反转原则,SOLID 由5个设计原则的头一个字母组成。

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

单一职责原则(Simple Responsibility Principle, SRP),这个原则的英文描述是这样的:

A class or module should have a single responsibility。
一个类或者模块只负责完成一个职责(或者功能)

注意,这个原则描述的对象包含两个:一个是类(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 detaildAddress; // 详细地址
	...
}

对于 UserInfo 类包含的信息有两种观点:

  • UserInfo 类包含的都是跟用户相关的信息,所有的属性和方法都隶属于用户这样一个业务模型,满足单一职责原则

  • UserInfo 类有地址信息相关的属性所占比重比较高,可以继续拆分成独立的 UserAddress 类,UserInfo 只保留除 Address 之外的其他信息,拆分之后的两个类的职责更加单一

实际上,要从中做出选择,我们不能脱离具体的应用场景:

  • 如果用户的地址信息跟其他信息一样,只是单纯用来展示,那 UserInfo 现在的设计就是合理的

  • 如果用户的地址信息还用在其他类或模块使用,比如产品中添加了电商的模块,用户的地址信息还会用在电商物流中,那我们最好将地址信息从 UserInfo 中拆分出来,独立成用户物流信息(或者叫地址信息、收获信息等)

  • 后续如果有新增登陆认证模块,就需要把 email、telephone 等抽取成独立的类

综上所述,评价一个类的职责是否足够单一,我们并没有一个非常明确的、可以量化的标准。不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否足够单一的判定都不一样

在真正的软件开发中,我们也没必要过于未雨绸缪、过度设计。我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构

虽然这个原则在量化和指标上并不是很清晰,但也可以借助一些小技巧来判定一个类的职责是否足够单一:

  • 类中的代码行数、函数或属性过多,会影响代码的可读性和可维护性,我们就需要考虑对类进行拆分

  • 类依赖的其他类过多,或者依赖类的其他类过多,不符合高内聚、低耦合的设计思想,我们就需要考虑对类进行拆分

  • 私有方法过多,我们就要考虑将私有方法独立到新的类中,设置为 public 方法,供更多的类使用,从而提高代码的复用性

  • 比较难给类起一个合适的名字,很难用一个业务名词概括,或者只用一些笼统的 ManagerContext 之类的词语来命名,这就说明类的职责定义得可能不够清晰

  • 类中大量的方法都是几种操作类中的某几个属性,比如,在 UserInfo 例子中,如果一半的方法都是在操作 address 信息,那就可以考虑将这几个属性和对应的方法拆分出来

怎么判定类的代码行数、函数、属性过多需要拆分?

  • 当一个类的代码,读起来让你头大了

  • 实现某个功能时不知道该用哪个函数了,想用哪个函数翻半天都找不到了

  • 只用到一个小功能就要引入整个类(类中包含很多无关此功能实现的函数)

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

为了满足单一职责原则,是不是把类拆分得越细越好呢?答案是否定的。还是通过一个例子来解释一下。

Serialization 类实现了一个简单协议的序列化和反序列化功能:

public class Serialization {
	private static final String IDENTIFIER_STRING = "UEUEUE;";
	private Gson gson;
	
	public String serialize(Map<String, String> object) {
		...
	}
	
	public Map<String, String> deserialize(String text) {
		...
	}
}

如果我们想让类的职责更加单一,我们对 Serialization 类进一步拆分,拆分成一个只负责序列化工作的 Serializer 和另一个只负责反序列化工作的 Deserializer 类:

public class Serializer {
	private static final String IDENTIFIER_STRING = "UEUEUE;";
	private Gson gson;

	public String serialize(Map<String, String> object) {
		...
	}
}

public class Deserializer {
	private static final String IDENTIFIER_STRING = "UEUEUE;";
	private Gson gson;

	public Map<String, String> deserialize(String text) {
		...
	}
}

拆分之后,Serializer 和 Deserializer 类的职责更加单一了,但也随之带来了问题:如果我们修改了 IDENTIFIER_STRING 协议的格式,数据标识从 UEUEUE 改为 DFDFDF,或者序列化方式从JSON改为了XML,那 Serializer 和 Deserializer 都需要做相应的修改,代码的内聚性显然没有原来的 Serialization 高了。而且,如果我们仅仅对 Serializer 做了协议修改,而忘记了修改 Deserializer,那就会导致序列化、反序列化不匹配,程序运行出错。也就是说,拆分之后,代码的可维护性变差了。

实际上,不管是应用设计原则还是设计模式,最终的目的还是提高代码的可读性、可扩展性、复用性、可维护性等。我们在考虑应用某一个设计原则是否合理的时候,也可以以此作为最终的考量标准。

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

总结

1、单一职责原则的理解

不要设计大而全的类,要设计粒度小、功能单一的类。

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

并没有一个非常明确的、可以量化的标准。不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否足够单一的判定都不一样。但可以通过一些小技巧来判定一个类的职业是否足够单一:

  • 类中的代码行数、函数或属性过多

  • 类依赖的其他类过多,或者依赖类的其他类过多

  • 私有方法过多

  • 比较难给类起一个合适的名字

  • 类中大量的方法都是几种操作类中的某几个属性

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

不是,设计原则和设计模式最主要解决的是代码的扩展性问题,代码要做到高内聚、低耦合,但拆分得太细反而会适得其反降低内聚性和代码地可维护性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值