1. 概述

本文将深入探讨面向对象设计中的 单一职责原则(SRP,Single Responsibility Principle),它是 SOLID 设计原则 的五大基石之一。

我们将详细解析 SRP 的核心思想、如何在实际项目中合理应用,以及一个常被忽视的问题:什么时候 SRP 反而会误导你
毕竟,原则是死的,人是活的,生搬硬套只会踩坑。

✅ SRP = 单一职责原则


2. 什么是单一职责原则

顾名思义,一个类应该只有一个职责,一个明确的目的
换句话说,这个类应该只做一件事,并且只因为这一件事而被修改。

❌ 如果一个类承担了多个不相关的功能,它就会变得“臃肿”,难以维护。一旦出问题,排查成本陡增。

举个例子:如果一个类频繁被修改,而且每次修改的原因各不相同,那它大概率已经违背了 SRP,应该被拆分。

来看一个看似合理、实则违规的示例:

public class TextManipulator {
    private String text;

    public TextManipulator(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    public void appendText(String newText) {
        text = text.concat(newText);
    }
    
    public String findWordAndReplace(String word, String replacementWord) {
        if (text.contains(word)) {
            text = text.replace(word, replacementWord);
        }
        return text;
    }
    
    public String findWordAndDelete(String word) {
        if (text.contains(word)) {
            text = text.replace(word, "");
        }
        return text;
    }

    public void printText() {
        System.out.println(textManipulator.getText());
    }
}

⚠️ 问题出在 printText() 方法——它负责输出文本,而其他方法都在处理文本内容。
这意味着这个类实际上承担了 两个职责

  1. 文本内容操作(append, replace, delete)
  2. 文本输出(print)

这明显违反了 SRP。

✅ 正确做法:职责分离

我们应该将“打印”功能剥离出去,创建一个专门负责输出的类:

public class TextPrinter {
    private TextManipulator textManipulator;

    public TextPrinter(TextManipulator textManipulator) {
        this.textManipulator = textManipulator;
    }

    public void printText() {
        System.out.println(textManipulator.getText());
    }

    public void printOutEachWordOfText() {
        System.out.println(Arrays.toString(textManipulator.getText().split(" ")));
    }

    public void printRangeOfCharacters(int startingIndex, int endIndex) {
        System.out.println(textManipulator.getText().substring(startingIndex, endIndex));
    }
}

这样,TextPrinter 就可以专注于各种打印变体,职责清晰,扩展性强。

💡 拆分后,维护和测试都更简单。比如要改打印格式,完全不需要动 TextManipulator


3. SRP 的“陷阱”:职责边界如何界定?

SRP 听起来简单,但落地时最容易踩的坑是:“到底什么叫一个职责?”

每个开发者对“职责”的理解可能不同,而 SRP 并没有给出明确的划分标准。这就导致:

  • 划分过粗 ❌:一个类干一堆事,违背 SRP
  • 划分过细 ❌:过度拆分,类爆炸,反而增加耦合和复杂度

最终,只有我们自己最清楚业务上下文,需要结合以下因素综合判断:

  • 业务领域模型
  • 实际需求场景
  • 系统整体架构

🎯 SRP 不是数学公式,不能机械套用。现实中的设计远比教程例子复杂。


4. 内聚性(Cohesion):判断职责的关键指标

遵循 SRP 的类,通常具有高内聚性(High Cohesion)——即类中的方法和属性都围绕一个明确目标展开。
高内聚意味着:

✅ 职责清晰
✅ 可维护性强
✅ 错误更少

如何判断是否该拆分?

回到 TextManipulator 的三个方法:

public void appendText(String newText) { ... }
public String findWordAndReplace(String word, String replacementWord) { ... }
public String findWordAndDelete(String word) { ... }

它们虽然操作不同,但都服务于“文本内容处理”这一核心目的,属于高内聚

⚠️ 如果强行拆分为 WriteTextUpdateText,反而会导致:

  • 两个类必须成对使用(紧耦合)
  • 职责割裂,逻辑不完整
  • 增加调用复杂度

这就是典型的“为了 SRP 而 SRP”,反而违背了设计初衷。

辅助工具:LCOM( Lack of Cohesion in Methods)

LCOM 是衡量类内部方法之间关联程度的指标:

  • LCOM 值越高 → 内聚性越低 → 越可能需要拆分
  • LCOM 值越低 → 内聚性越高 → 职责越单一

其中,LCOM4 是一个经典变体,曾被 SonarQube 用作代码质量检测指标(现已弃用)。

💡 虽然工具已过时,但“高内聚”这一设计思想依然重要。


5. 总结

  • ✅ SRP 的核心是“一个类只因一个原因被修改”
  • ✅ 职责划分要结合业务,避免过度拆分或粗粒度合并
  • ✅ 借助“内聚性”判断职责边界,比死记硬背原则更有效
  • ⚠️ 不要为了 SRP 而 SRP,设计要服务于实际需求

本文示例代码已上传至 GitHub:https://github.com/baeldung/tutorials/tree/master/patterns-modules/solid


原始标题:Single Responsibility Principle in Java | Baeldung