单一职责原则:一个类只做一件事

各位同学,今天我们来聊聊设计模式里最基础、也最容易被忽视的一条原则——单一职责原则。

说白了,就是一句话:一个类,只应该有一个引起它变化的原因

听起来很简单对吧?但我见过太多项目,一个类动辄上千行,什么逻辑都往里塞。嗯,我早期也犯过这个错。有一次接手一个老系统,有个叫 OrderService 的类,里面既有订单校验、又有库存扣减、还有发送邮件……改一个功能,牵一发动全身,测试要跑半天。那滋味,真不好受。

什么是单一职责?

单一职责原则(Single Responsibility Principle,SRP)是 SOLID 原则的第一条。它强调:一个类应该只有一个职责,并且这个职责应该被完整地封装在类中

你想想看,如果一个类承担了多个职责,那么当其中一个职责发生变化时,就可能影响到其他职责。这就好比一个厨师既要炒菜、又要收银、还要打扫卫生——任何一个环节出问题,整个餐厅都会乱套。

核心要点:

  • 每个类只负责一个功能领域
  • 类的变化原因只有一个
  • 高内聚:类内部紧密相关
  • 低耦合:类之间依赖最小化

高内聚与低耦合

这两个词,你肯定听过无数遍了。但我还是想用自己的理解给你讲透。

高内聚,指的是一个类内部的方法和数据,都围绕同一个目标服务。比如一个 UserValidator 类,所有方法都是关于用户数据校验的——校验邮箱、校验手机号、校验密码强度。这些方法之间关系紧密,这就是高内聚。

低耦合,指的是类与类之间的依赖关系尽量简单。一个类不应该知道太多其他类的内部细节。我习惯用「接口隔离」和「依赖注入」来实现低耦合,后面实战中你会看到。

我的经验:

判断一个类是否职责过重,有个简单方法:试着用一句话描述这个类「做了什么」。如果这句话里出现了「和」、「并且」、「同时」这些词,那大概率是职责过多了。

违反 SRP 的典型症状

我在代码评审时,经常看到这些「坏味道」:

  • 类名带「And」或「Util」——比如 UserAndOrderUtil,一看就是大杂烩
  • 一个类有多个不相关的字段——比如用户类里混入了订单状态
  • 一个方法做了多件事——比如既查询数据库,又发通知,还写日志
  • 修改一个功能需要改多个地方——这是最直接的信号

避坑指南:

我曾经在一个项目中,把「用户注册」和「用户登录」放在同一个 Service 里。结果后来需求变了,注册要加短信验证,登录要加图形验证码。两个逻辑纠缠在一起,改得我头皮发麻。从那以后,我坚持一个 Service 只做一件事。

实战:重构 UserService

好,理论说完了,我们来看一个真实的例子。

假设你接手了一个老项目,里面有这样一个 UserService

public class UserService {
    public void register(String username, String password, String email) {
        // 1. 校验参数
        if (username == null || username.isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        if (password == null || password.length() < 6) {
            throw new IllegalArgumentException("密码长度不能少于6位");
        }
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("邮箱格式不正确");
        }

        // 2. 保存用户
        User user = new User(username, password, email);
        // 假设有数据库操作
        saveToDatabase(user);

        // 3. 发送欢迎邮件
        sendWelcomeEmail(email);

        // 4. 记录日志
        log("用户注册成功: " + username);
    }

    private void saveToDatabase(User user) {
        // 数据库操作
    }

    private void sendWelcomeEmail(String email) {
        // 发送邮件
    }

    private void log(String message) {
        // 写日志
    }
}

这个类有什么问题?

问题大了去了。它至少承担了四个职责:参数校验、数据持久化、邮件发送、日志记录。任何一个职责变化,都要修改这个类。而且,这些逻辑无法复用——比如其他地方也要校验邮箱,还得再写一遍。

我们按照 SRP 来重构。先画出职责拆分图:

UserService(原始) UserValidator UserRepository EmailService LoggerService UserRegistrationService(组合) 每个类只负责一个职责

重构后的代码,我们拆成四个独立的类:

// 1. 参数校验类
public class UserValidator {
    public void validate(String username, String password, String email) {
        if (username == null || username.isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        if (password == null || password.length() < 6) {
            throw new IllegalArgumentException("密码长度不能少于6位");
        }
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("邮箱格式不正确");
        }
    }
}

// 2. 数据持久化类
public class UserRepository {
    public void save(User user) {
        // 数据库操作
    }
}

// 3. 邮件服务类
public class EmailService {
    public void sendWelcomeEmail(String email) {
        // 发送邮件
    }
}

// 4. 日志服务类
public class LoggerService {
    public void log(String message) {
        // 写日志
    }
}

// 5. 组合类——只负责编排
public class UserRegistrationService {
    private UserValidator validator;
    private UserRepository repository;
    private EmailService emailService;
    private LoggerService logger;

    public UserRegistrationService(
            UserValidator validator,
            UserRepository repository,
            EmailService emailService,
            LoggerService logger) {
        this.validator = validator;
        this.repository = repository;
        this.emailService = emailService;
        this.logger = logger;
    }

    public void register(String username, String password, String email) {
        validator.validate(username, password, email);
        User user = new User(username, password, email);
        repository.save(user);
        emailService.sendWelcomeEmail(email);
        logger.log("用户注册成功: " + username);
    }
}

重构后的好处:

  • 可测试性提升:每个类都可以单独写单元测试
  • 可复用性提升UserValidator 可以在登录、修改密码等场景复用
  • 可维护性提升:改邮件逻辑只动 EmailService,不会影响其他代码
  • 扩展性提升:想加短信通知?加个 SmsService 就行,不用改现有类

什么时候可以「适度」违反?

你可能会问:是不是所有类都必须严格遵循 SRP?

我的回答是:原则是指导,不是教条

在以下场景,我偶尔会「适度」放宽:

场景 说明
工具类 比如 StringUtils,虽然包含多个字符串操作方法,但都属于「字符串处理」这个大类
简单 CRUD 如果业务逻辑极其简单,强行拆分反而增加复杂度
性能敏感 某些高频调用场景,过度拆分可能带来性能损耗

我的建议:

先按 SRP 写,等代码稳定后,再通过性能分析工具决定是否需要合并。不要一开始就为了「省事」而违反原则——省事的代价,往往是后期维护的噩梦。

总结

单一职责原则,说白了就是让每个类「各司其职」。它不是什么高深的理论,而是写代码时的一种习惯。

我个人的习惯是:写一个新类之前,先问自己三个问题:

  1. 这个类到底要做什么?
  2. 它的职责是否单一?
  3. 如果需求变了,改这个类会不会影响其他功能?

如果第三个问题的答案是「会」,那就说明职责没分清楚,需要重新设计。

记住:好的代码不是写出来的,是「拆」出来的。把大问题拆成小问题,把大职责拆成小职责,代码自然就优雅了。


公众号:蓝海资料掘金营,微信deep3321