← Назад к вопросам

Приведи пример S из SOLID

1.8 Middle🔥 171 комментариев
#SOLID и паттерны проектирования#ООП

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Приведи пример S из SOLID

S — Single Responsibility Principle (Принцип единственной ответственности)

Класс должен иметь только одну причину для изменения. Он должен отвечать за одно и только одно.

Плохой пример: нарушение SRP

public class User {
    private String name;
    private String email;
    private String password;
    
    // Ответственность 1: управление данными пользователя
    public void setPassword(String password) {
        this.password = password;
    }
    
    // Ответственность 2: валидация
    public boolean isValidEmail() {
        return email.contains("@");
    }
    
    // Ответственность 3: сохранение в БД
    public void saveToDatabase() {
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/users");
        PreparedStatement ps = conn.prepareStatement("INSERT INTO users VALUES (?, ?, ?)");
        ps.setString(1, name);
        ps.setString(2, email);
        ps.setString(3, password);
        ps.executeUpdate();
    }
    
    // Ответственность 4: отправка письма
    public void sendWelcomeEmail() {
        String emailBody = "Welcome, " + name + "!";
        // SMTP отправка...
    }
    
    // Ответственность 5: логирование
    public void log(String message) {
        System.out.println("[" + System.currentTimeMillis() + "] " + message);
    }
}

Проблемы:

  • User класс отвечает за 5 разных вещей
  • Если изменится схема БД → изменяем User
  • Если изменится email сервис → изменяем User
  • Если изменится формат логирования → изменяем User
  • Класс сложный, трудно тестировать, трудно переиспользовать

Хороший пример: соблюдение SRP

Разделяем ответственность на разные классы:

1. Класс для данных пользователя:

public class User {
    private String name;
    private String email;
    private String password;
    
    public User(String name, String email, String password) {
        this.name = name;
        this.email = email;
        this.password = password;
    }
    
    public String getName() { return name; }
    public String getEmail() { return email; }
    public String getPassword() { return password; }
}

2. Класс для валидации:

public class UserValidator {
    public boolean isValidEmail(String email) {
        return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
    }
    
    public boolean isValidPassword(String password) {
        return password.length() >= 8;
    }
}

3. Класс для работы с БД:

public class UserRepository {
    private DataSource dataSource;
    
    public UserRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    public void save(User user) {
        String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)";
        try (Connection conn = dataSource.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, user.getName());
            ps.setString(2, user.getEmail());
            ps.setString(3, user.getPassword());
            ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException("Failed to save user", e);
        }
    }
    
    public User findByEmail(String email) {
        // SELECT * FROM users WHERE email = ?
        return new User(...);
    }
}

4. Класс для отправки писем:

public class EmailService {
    private JavaMailSender mailSender;
    
    public EmailService(JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }
    
    public void sendWelcomeEmail(User user) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(user.getEmail());
        message.setSubject("Welcome!");
        message.setText("Welcome, " + user.getName() + "!");
        mailSender.send(message);
    }
}

5. Класс для логирования:

public class Logger {
    public void log(String level, String message) {
        System.out.println("[" + level + "] " + message);
    }
}

6. Класс для бизнес-логики (use case):

public class CreateUserUseCase {
    private final UserValidator validator;
    private final UserRepository repository;
    private final EmailService emailService;
    private final Logger logger;
    
    public CreateUserUseCase(UserValidator validator, 
                            UserRepository repository,
                            EmailService emailService,
                            Logger logger) {
        this.validator = validator;
        this.repository = repository;
        this.emailService = emailService;
        this.logger = logger;
    }
    
    public void createUser(String name, String email, String password) {
        logger.log("INFO", "Creating user: " + email);
        
        // Валидация
        if (!validator.isValidEmail(email)) {
            throw new IllegalArgumentException("Invalid email");
        }
        if (!validator.isValidPassword(password)) {
            throw new IllegalArgumentException("Password too short");
        }
        
        // Создание пользователя
        User user = new User(name, email, password);
        
        // Сохранение в БД
        repository.save(user);
        
        // Отправка письма
        emailService.sendWelcomeEmail(user);
        
        logger.log("INFO", "User created successfully: " + email);
    }
}

Преимущества SRP

  1. Простота в тестировании:
@Test
public void testUserValidator() {
    UserValidator validator = new UserValidator();
    assertTrue(validator.isValidEmail("test@example.com"));
    assertFalse(validator.isValidEmail("invalid"));
}

@Test
public void testEmailService() {
    EmailService service = new EmailService(mockMailSender);
    User user = new User("John", "john@example.com", "password123");
    service.sendWelcomeEmail(user);
    verify(mockMailSender).send(any());
}
  1. Легко переиспользовать:
// Можем использовать EmailService в других местах
public class PasswordResetService {
    private EmailService emailService;
    
    public void resetPassword(User user) {
        // ...
        emailService.sendPasswordResetEmail(user);
    }
}
  1. Легко менять реализацию:
// Хотим менять EmailService с SMTP на SMS?
public interface NotificationService {
    void notify(User user, String message);
}

public class SMTPEmailService implements NotificationService {
    // ...
}

public class SMSService implements NotificationService {
    // ...
}

public class CreateUserUseCase {
    private NotificationService notificationService;  // Не зависит от реализации!
    // ...
}
  1. Класс легче понять:
    • User знает только о пользователе
    • UserRepository знает только о БД
    • EmailService знает только об отправке писем

Правило большого пальца

Если вы не можете описать класс одним предложением БЕЗ слов "и", "также", "плюс" — класс имеет больше одной ответственности:

  • ❌ "User класс управляет пользователем И сохраняет его в БД И отправляет письма"
  • ✓ "User класс хранит данные пользователя"
  • ✓ "UserRepository класс сохраняет и читает пользователей из БД"
  • ✓ "EmailService класс отправляет письма пользователям"

Итоги

Single Responsibility Principle:

  • Один класс = одна ответственность
  • Одна причина для изменения
  • Улучшает тестируемость, переиспользуемость и поддерживаемость
  • Делает код более гибким и расширяемым
Приведи пример S из SOLID | PrepBro