跳到主要内容

单一职责原则

什么是单一职责原则

单一职责原则(Single Responsibility Principle,简称 SRP)是 SOLID 原则中的第一个原则。其英文描述为:A class or module should have a single responsibility。它的定义是:一个类或者模块只负责完成一个职责(或者功能)

这个原则主要强调:

  • 不要设计大而全的类
  • 一个类应该只关注一个功能领域
  • 一个类的变化原因应该只有一个

从类和模块的维度理解

在理解单一职责原则时,需要注意其描述对象包含两个维度:类(class)和模块(module)。对这两个概念,我们有两种理解方式:

  1. 从抽象层面理解:

    • 模块是比类更加抽象的概念
    • 类可以看作是模块的一种具体实现
    • 单一职责原则适用于任何抽象层次的封装
  2. 从粒度层面理解:

    • 模块是比类更加粗粒度的代码块
    • 一个模块可能包含多个类
    • 多个类通过协作来实现模块的职责

无论采用哪种理解方式,单一职责原则的核心思想都是一致的:一个代码单元(无论是类还是模块)都应该只负责一个明确的功能领域。

如何判断职责是否单一

判断一个类的职责是否足够单一并没有统一的量化标准,这是一个比较主观的判断。但我们可以通过以下几个方面来进行评估:

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());
}
}

过度拆分的问题:

  1. 如果修改协议格式,需要同时修改两个类
  2. 可能导致序列化和反序列化不匹配
  3. 降低了代码的内聚性和可维护性

总结

单一职责原则的核心目标是提高代码的:

  • 可读性
  • 可维护性
  • 可复用性
  • 可扩展性

在实际应用中要注意:

  1. 根据具体业务场景判断职责是否单一
  2. 避免过度拆分
  3. 在保持代码质量的同时兼顾开发效率
  4. 持续重构而不是追求一步到位

记住,单一职责原则不是目的,而是手段。最终目标是写出高质量的代码。