一、前言:为什么你的代码越来越难维护?

不知道你有没有遇到过这种情况:一个类写了几百上千行,里面塞了各种不相关的方法,改个小功能生怕影响其他地方,单元测试写起来也特别费劲。
这大概率就是单一职责原则(Single Responsibility Principle, SRP) 没遵守好的锅。它是 SOLID 五大设计原则的 “老大哥”,核心定义很简单:一个类应该只有一个引起它变化的原因,也就是只承担一个职责
今天我们就以 Java 登录功能为例,手把手教你用单一职责原则重构代码,看看它到底有多香!

二、问题分析:原来的 Login 类到底有多 “臃肿”?

先看一下我们原始的Login类,它包含了这些方法:

  • init():初始化操作
  • display():界面展示逻辑
  • validate():参数校验
  • getConnection():获取数据库连接
  • findUser():查询用户信息
  • main():程序入口

问题出在哪?

这个类简直是个 “全能选手”,同时承担了 4 种完全不同的职责:
1.界面交互:display()负责展示登录界面
2.业务校验:validate()负责参数合法性校验
3.数据访问:getConnection()和findUser()负责数据库操作
4.程序入口:main()作为启动入口
这就导致了严重的耦合问题:

  • 需求变了要改界面?可能不小心就影响到数据库逻辑
  • 换个数据库连接方式?界面相关的代码也要跟着改
  • 想复用数据库查询逻辑?只能把整个 Login 类搬过去,带一堆没用的界面方法

这完全违背了单一职责原则,是典型的 “上帝类” 设计。
某基于Java的C/S系统的“登录功能”通过登录类(Login)实现
某基于Java的C/S系统的“登录功能”通过如上登录类(Login)实现。

三、重构方案:按职责拆分,各司其职

在这里插入图片描述
根据单一职责原则,我们把原来的Login类拆分成 4 个独立的类,每个类只做一件事:

1.UI交互类:LoginView

只负责界面展示和用户输入获取,不处理任何业务逻辑。

public class LoginView {
    // 展示登录界面
    public void display() {
        System.out.println("===== 用户登录 =====");
        System.out.print("请输入用户名:");
    }

    // 获取用户输入
    public String getUsername() {
        Scanner scanner = new Scanner(System.in);
        return scanner.nextLine();
    }

    public String getPassword() {
        System.out.print("请输入密码:");
        Scanner scanner = new Scanner(System.in);
        return scanner.nextLine();
    }

    // 展示登录结果
    public void showResult(boolean isSuccess) {
        if (isSuccess) {
            System.out.println("登录成功!");
        } else {
            System.out.println("用户名或密码错误!");
        }
    }
}

2.业务逻辑校验类:LoginValidator

只负责参数合法性校验,比如用户名 / 密码是否为空、长度是否符合要求。

public class LoginValidator {
    // 校验用户名和密码是否合法
    public boolean validate(String username, String password) {
        if (username == null || username.trim().isEmpty()) {
            System.out.println("用户名不能为空!");
            return false;
        }
        if (password == null || password.length() < 6) {
            System.out.println("密码长度不能少于6位!");
            return false;
        }
        return true;
    }
}

3.数据访问类:UserDAO

只负责和数据库交互,获取连接、查询用户信息,不关心界面和业务逻辑。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class UserDAO {
    // 获取数据库连接
    public Connection getConnection() throws Exception {
        Class.forName("com.mysql.cj.jdbc.Driver");
        return DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/test", "root", "password");
    }

    // 根据用户名和密码查询用户
    public boolean findUser(String username, String password) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            String sql = "SELECT * FROM user WHERE username=? AND password=?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, username);
            pstmt.setString(2, password);
            rs = pstmt.executeQuery();
            return rs.next();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            // 关闭资源(简化写法)
            try { if (rs != null) rs.close(); } catch (Exception e) {}
            try { if (pstmt != null) pstmt.close(); } catch (Exception e) {}
            try { if (conn != null) conn.close(); } catch (Exception e) {}
        }
    }
}

4.业务控制类:LoginService

负责串联各个模块,处理登录的核心流程,但不直接处理界面和数据库操作。

public class LoginService {
    private LoginValidator validator;
    private UserDAO userDAO;

    public LoginService() {
        this.validator = new LoginValidator();
        this.userDAO = new UserDAO();
    }

    // 登录核心流程
    public boolean login(String username, String password) {
        // 1. 参数校验
        if (!validator.validate(username, password)) {
            return false;
        }
        // 2. 查询用户
        return userDAO.findUser(username, password);
    }
}

5.程序入口类:MainClass

只负责启动程序,调用业务逻辑,不包含其他功能。

public class MainClass {
    public static void main(String[] args) {
        LoginView view = new LoginView();
        LoginService service = new LoginService();

        // 1. 展示界面并获取输入
        view.display();
        String username = view.getUsername();
        String password = view.getPassword();

        // 2. 调用登录服务
        boolean isSuccess = service.login(username, password);

        // 3. 展示结果
        view.showResult(isSuccess);
    }
}

四、写在最后:单一职责原则的核心思想

很多人会误解单一职责原则,觉得 “拆分得越细越好”,其实不是这样的。它的核心是把变化的原因隔离开,让一个类只有一个变化的理由。
比如上面的例子:

  • 界面变了,只需要改LoginView
  • 校验规则变了,只需要改LoginValidator
  • 数据库换了,只需要改UserDAO
  • 登录流程调整,只需要改LoginService

这样的代码,不仅维护起来轻松,团队协作时也不容易冲突,后续扩展功能也更方便。

希望这个简单的登录功能重构案例,能帮你真正理解单一职责原则。下次写代码时,不妨停下来想一想:这个类是不是承担了太多职责?能不能拆分成更独立的模块?

小彩蛋:常见误区提醒

1.不要过度拆分:不是每个方法都要单独建类,比如只有一两行的工具方法,没必要为了拆分而拆分,反而会增加复杂度。
2.关注 “变化的原因”:判断是否需要拆分,关键看这个类的修改频率和修改原因,如果不同的需求会导致类被频繁修改,就该考虑拆分了。
3.SRP 也适用于方法和模块:不仅类要遵守单一职责,方法、模块也应该只做一件事,比如一个方法不要既查数据库又处理业务还发邮件。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐