单一职责原则
什么是单一职责原则
单一职责原则(Single Responsibility Principle,简称 SRP)是 SOLID 原则中的第一个原则。其英文描述为:A class or module should have a single responsibility。它的定义是:一个类或者模块只负责完成一个职责(或者功能)。
这个原则主要强调:
- 不要设计大而全的类
- 一个类应该只关注一个功能领域
- 一个类的变化原因应该只有一个
从类和模块的维度理解
在理解单一 职责原则时,需要注意其描述对象包含两个维度:类(class)和模块(module)。对这两个概念,我们有两种理解方式:
-
从抽象层面理解:
- 模块是比类更加抽象的概念
- 类可以看作是模块的一种具体实现
- 单一职责原则适用于任何抽象层次的封装
-
从粒度层面理解:
- 模块是比类更加粗粒度的代码块
- 一个模块可能包含多个类
- 多个类通过协作来实现模块的职责
无论采用哪种理解方式,单一职责原则的核心思想都是一致的:一个代码单元(无论是类还是模块)都应该只负责一个明确的功能领域。
如何判断职责是否单一
判断一个类的职责是否足够单一并没有统一的量化标准,这是一个比较主观的判断。但我们可以通过以下几个方面来进行评估:
1. 代码特征判断
- 类中的代码行数、函数或属性过多
- 类依赖的其他类过多,或者被其他类过多地依赖
- 私有方法过多
- 难以给类起一个合适的名字
- 类中大量方法都是集中操作某几个属性
2. 业务场景判断
不同的应用场景、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。
例如,一个 UserInfo 类:
public class UserInfo {
private Long userId;
private String username;
private String email;
private String telephone;
private String provinceOfAddress;
private String cityOfAddress;
private String regionOfAddress;
private String detailedAddress;
// ...其他属性
}
在不同场景下的判断:
- 如果只是展示用户信息,当前设计可能已经满足单一职责
- 如果要支持电商功能,地址信息可能需要独立成 UserAddress 类
- 如果要支持统一账号系统,身份认证信息(email、telephone)可能需要独立成 UserAuth 类
实践建议
1. 循序渐进的重构
- 先写一个粗粒度的类满足业务需求
- 随着业务发展,如果类变得庞大,再进行拆分
- 持续进行重构,而不是一次性完成
2. 参考性能指标
对于没有太多项目经验的开发者,可以参考以下指标:
- 一个类的代码行数最好不超过 200 行
- 函数个数及属性个数都最好不超过 10 个
3. 避免过度拆分
过度拆分可能带来新的问题:
- 降低内聚性
- 增加类之间的耦合
- 影响代码的可维护性
示例分析
让我们通过一个序列化功能的例子来说明过度拆分的问题:
// 原始设计
public class Serialization {
private static final String IDENTIFIER = "UEUEUE;";
private Gson gson = new Gson();
public String serialize(Map<String, String> object) {
StringBuilder textBuilder = new StringBuilder();
textBuilder.append(IDENTIFIER);
textBuilder.append(gson.toJson(object));
return textBuilder.toString();
}
public Map<String, String> deserialize(String text) {
if (!text.startsWith(IDENTIFIER)) {
return Collections.emptyMap();
}
String jsonStr = text.substring(IDENTIFIER.length());
return gson.fromJson(jsonStr, new TypeToken<Map<String, String>>(){}.getType());
}
}
// 过度拆分的设计
public class Serializer {
private static final String IDENTIFIER = "UEUEUE;";
private Gson gson = new Gson();
public String serialize(Map<String, String> object) {
StringBuilder textBuilder = new StringBuilder();
textBuilder.append(IDENTIFIER);
textBuilder.append(gson.toJson(object));
return textBuilder.toString();
}
}
public class Deserializer {
private static final String IDENTIFIER = "UEUEUE;";
private Gson gson = new Gson();
public Map<String, String> deserialize(String text) {
if (!text.startsWith(IDENTIFIER)) {
return Collections.emptyMap();
}
String jsonStr = text.substring(IDENTIFIER.length());
return gson.fromJson(jsonStr, new TypeToken<Map<String, String>>(){}.getType());
}
}
过度拆分的问题:
- 如果修改协议格式,需要同时修改两个类
- 可能导致序列化和反序列化不匹配
- 降低了代码的内聚性和可维护性
总结
单一职责原则的核心目标是提高代码的:
- 可读性
- 可维护性
- 可复用性
- 可扩展性
在实际应用中要注意:
- 根据具体业务场景判断职责是否单一
- 避免过度拆分
- 在保持代码质量的同时兼顾开发效率
- 持续重构而不是追求一步到位
记住,单一职责原则不是目的,而是手段。最终目标是写出高质量的代码。