mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
107 lines
6.9 KiB
Markdown
107 lines
6.9 KiB
Markdown
# SOLID之单一职责 SRP
|
||
|
||
单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。这个原则的英文描
|
||
述是这样的:A class or module should have a single reponsibility。如果我们把它翻译
|
||
成中文,那就是:一个类或者模块只负责完成一个职责(或者功能)。
|
||
|
||
|
||
一个类里既包含订单的一些操作,又包含用户的一些操作。而订单和用户是两个独立的业务领域模型,我们将两个不相干的功能放到同一个类中,那就违反了单一职责原则。为了满足单一职责原则,我们需要将这个类拆分成两个粒度更细、功能更加单一的两个类:订单类和用户类。
|
||
|
||
## 如何判断类的职责是否足够单一?
|
||
在一个社交产品中,我们用下面的 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 中拆分出来,独立成用户物流信息(或者叫地址信息、收货信息等)。
|
||
|
||
我们再进一步延伸一下。如果做这个社交产品的公司发展得越来越好,公司内部又开发出了跟多其他产品(可以理解为其他 App)。公司希望支持统一账号系统,也就是用户一个账号可以在公司内部的所有产品中登录。这个时候,我们就需要继续对 UserInfo 进行拆分,将跟身份认证相关的信息(比如,email、telephone 等)抽取成独立的类。
|
||
|
||
从刚刚这个例子,我们可以总结出,不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,可能都是不一样的。在某种应用场景或者当下的需求背景下,一个类的设计可能已经满足单一职责原则了,但如果换个应用场景或着在未来的某个需求背景下,可能就不满足了,需要继续拆分成粒度更细的类。
|
||
|
||
|
||
评价一个类的职责是否足够单一,我们并没有一个非常明确的、可以量化的标准,可以说,这是件非常主观、仁者见仁智者见智的事情。实际上,在真正的软件开发中,
|
||
我们也没必要过于未雨绸缪,过度设计。所以,我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构。
|
||
|
||
|
||
## 类的职责是否设计得越单一越好?
|
||
为了满足单一职责原则,是不是把类拆得越细就越好呢?答案是否定的。我们还是通过一个例子来解释一下。Serialization 类实现了一个简单协议的序列化和反序列功能,具体代码如下:
|
||
```
|
||
/**
|
||
* Protocol format: identifier-string;{gson string}
|
||
* For example: UEUEUE;{"a":"A","b":"B"}
|
||
*/
|
||
public class Serialization {
|
||
private static final String IDENTIFIER_STRING = "UEUEUE;";
|
||
private Gson gson;
|
||
public Serialization() {
|
||
public class Serialization {
|
||
this.gson = new Gson();
|
||
}
|
||
public String serialize(Map<String, String> object) {
|
||
StringBuilder textBuilder = new StringBuilder();
|
||
textBuilder.append(IDENTIFIER_STRING);
|
||
textBuilder.append(gson.toJson(object));
|
||
return textBuilder.toString();
|
||
}
|
||
public Map<String, String> deserialize(String text) {
|
||
if (!text.startsWith(IDENTIFIER_STRING)) {
|
||
return Collections.emptyMap();
|
||
}
|
||
String gsonStr = text.substring(IDENTIFIER_STRING.length());
|
||
return gson.fromJson(gsonStr, Map.class);
|
||
}
|
||
}
|
||
```
|
||
如果我们想让类的职责更加单一,我们对 Serialization 类进一步拆分,拆分成一个只负责序列化工作的 Serializer 类和另一个只负责反序列化工作的 Deserializer 类。拆分后的具体代码如下所示:
|
||
```
|
||
public class Serializer {
|
||
private static final String IDENTIFIER_STRING = "UEUEUE;";
|
||
private Gson gson;
|
||
public Serializer() {
|
||
this.gson = new Gson();
|
||
}
|
||
public String serialize(Map<String, String> object) {
|
||
StringBuilder textBuilder = new StringBuilder();
|
||
textBuilder.append(IDENTIFIER_STRING);
|
||
textBuilder.append(gson.toJson(object));
|
||
return textBuilder.toString();
|
||
}
|
||
}
|
||
|
||
public class Deserializer {
|
||
private static final String IDENTIFIER_STRING = "UEUEUE;";
|
||
private Gson gson;
|
||
public Deserializer() {
|
||
this.gson = new Gson();
|
||
}
|
||
|
||
public Map<String, String> deserialize(String text) {
|
||
if (!text.startsWith(IDENTIFIER_STRING)) {
|
||
return Collections.emptyMap();
|
||
}
|
||
String gsonStr = text.substring(IDENTIFIER_STRING.length());
|
||
return gson.fromJson(gsonStr, Map.class);
|
||
}
|
||
}
|
||
```
|
||
虽然经过拆分之后,Serializer 类和 Deserializer 类的职责更加单一了,但也随之带来了新的问题。如果我们修改了协议的格式,数据标识从“UEUEUE”改为“DFDFDF”,或者序列化方式从 JSON 改为了 XML,那 Serializer 类和 Deserializer 类都需要做相应的修改,代码的内聚性显然没有原来 Serialization 高了。而且,如果我们仅仅对 Serializer 类做了协议修改,而忘记了修改 Deserializer 类的代码,那就会导致序列化、反序列化不匹配,程序运行出错,也就是说,拆分之后,代码的可维护性变差了。实际上,不管是应用设计原则还是设计模式,最终的目的还是提高代码的可读性、可扩展性、复用性、可维护性等。我们在考虑应用某一个设计原则是否合理的时候,也可以以此作为最终的考量标准。
|
||
|
||
|