← Назад к вопросам
Что такое принцип единственной ответственности (SRP, Single Responsibility Principle) в ООП?
1.7 Middle🔥 231 комментариев
#SOLID и паттерны проектирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ
Принцип единственной ответственности (Single Responsibility Principle, SRP) — один из пяти SOLID принципов. Он гласит, что класс должен иметь одну и только одну причину для изменения. Другими словами, класс должен отвечать только за одно.
1. Основная идея SRP
// НЕПРАВИЛЬНО: Класс имеет множество ответственностей
public class User {
private String name;
private String email;
private String password;
// Ответственность 1: Управление данными пользователя
public void setName(String name) {
this.name = name;
}
// Ответственность 2: Проверка email
public boolean isValidEmail() {
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
// Ответственность 3: Хеширование пароля
public String hashPassword(String password) {
return new BCryptPasswordEncoder().encode(password);
}
// Ответственность 4: Отправка email
public void sendWelcomeEmail() {
// логика отправки email
}
// Ответственность 5: Логирование
public void logActivity() {
System.out.println("User activity: " + this.name);
}
// Ответственность 6: Взаимодействие с БД
public void saveToDatabase() {
// SQL запросы
}
}
Проблемы:
- Класс слишком сложный
- Сложно тестировать
- Изменение email валидации требует изменения класса User
- Изменение логирования требует изменения класса User
2. Правильное применение SRP
Разделяем на несколько классов, каждый с одной ответственностью:
// Класс 1: Управление данными пользователя
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@Column(name = "name")
private String name;
@Column(name = "email")
private String email;
@Column(name = "password_hash")
private String password;
// Только getters и setters — нет логики
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
// Класс 2: Валидация email
public class EmailValidator {
public boolean isValid(String email) {
return email != null && email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
}
// Класс 3: Работа с паролями
@Component
public class PasswordService {
@Autowired
private PasswordEncoder passwordEncoder;
public String hashPassword(String plainPassword) {
return passwordEncoder.encode(plainPassword);
}
public boolean isPasswordValid(String plainPassword, String hash) {
return passwordEncoder.matches(plainPassword, hash);
}
}
// Класс 4: Отправка email
@Service
public class EmailService {
@Autowired
private JavaMailSender mailSender;
public void sendWelcomeEmail(String to, String name) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject("Welcome " + name);
message.setText("Welcome to our service!");
mailSender.send(message);
}
}
// Класс 5: Логирование
@Component
public class UserActivityLogger {
private static final Logger logger = LoggerFactory.getLogger(UserActivityLogger.class);
public void logUserActivity(String username, String activity) {
logger.info("User {} performed: {}", username, activity);
}
}
// Класс 6: Работа с БД
@Repository
public interface UserRepository extends JpaRepository<User, String> {
Optional<User> findByEmail(String email);
}
// Класс 7: Ввод-вывод (Controller)
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<?> createUser(@RequestBody CreateUserRequest request) {
return ResponseEntity.ok(userService.createUser(request));
}
}
// Оркестратор: Координирует работу всех компонентов
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailValidator emailValidator;
@Autowired
private PasswordService passwordService;
@Autowired
private EmailService emailService;
@Autowired
private UserActivityLogger logger;
@Transactional
public User createUser(CreateUserRequest request) {
// Валидация email
if (!emailValidator.isValid(request.getEmail())) {
throw new IllegalArgumentException("Invalid email");
}
// Хеширование пароля
String hashedPassword = passwordService.hashPassword(request.getPassword());
// Создание пользователя
User user = new User();
user.setName(request.getName());
user.setEmail(request.getEmail());
user.setPassword(hashedPassword);
// Сохранение в БД
User savedUser = userRepository.save(user);
// Отправка email
emailService.sendWelcomeEmail(user.getEmail(), user.getName());
// Логирование
logger.logUserActivity(user.getName(), "User registered");
return savedUser;
}
}
public class CreateUserRequest {
private String name;
private String email;
private String password;
// getters
}
3. Архитектурный подход
У каждого слоя своя ответственность:
┌─────────────────────────────────────┐
│ Presentation (Controller) │ ← Обработка HTTP запросов
├─────────────────────────────────────┤
│ Application (Service) │ ← Бизнес-логика
├─────────────────────────────────────┤
│ Domain (Entities, ValueObjects) │ ← Модели данных
├─────────────────────────────────────┤
│ Infrastructure (Repository, DB) │ ← Работа с БД
└─────────────────────────────────────┘
// Domain слой: только сущности
public class User {
private String id;
private String name;
// Только данные
}
// Application слой: бизнес-логика
@Service
public class UserService {
// Координирует взаимодействие
}
// Infrastructure слой: работа с технологиями
@Repository
public interface UserRepository extends JpaRepository<User, String> {
// Работа с БД
}
4. Практический пример с PaymentProcessor
// НЕПРАВИЛЬНО: Одна большая ответственность
public class PaymentProcessor {
public void processPayment(Order order) {
// Валидация
if (order.getTotal() <= 0) {
throw new IllegalArgumentException();
}
// Вызов API платёжной системы
String response = callPaymentAPI(order);
// Парсинг ответа
PaymentResult result = parseResponse(response);
// Обновление заказа
order.setStatus("paid");
// Отправка email
sendPaymentConfirmationEmail(order);
// Логирование
logPayment(order, result);
}
}
Правильно: Разделяем ответственности
// Класс 1: Валидация платежей
public class PaymentValidator {
public void validate(Order order) throws ValidationException {
if (order.getTotal() <= 0) {
throw new ValidationException("Invalid amount");
}
}
}
// Класс 2: Интеграция с платёжной системой
@Component
public class PaymentGateway {
public PaymentResult process(Order order) {
String response = callPaymentAPI(order);
return parseResponse(response);
}
}
// Класс 3: Обновление статуса заказа
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public void markAsPaid(Order order) {
order.setStatus("paid");
order.setPaidAt(LocalDateTime.now());
orderRepository.save(order);
}
}
// Класс 4: Уведомления
@Service
public class NotificationService {
public void sendPaymentConfirmation(Order order) {
// Отправка email
}
}
// Класс 5: Логирование платежей
@Component
public class PaymentLogger {
private static final Logger logger = LoggerFactory.getLogger(PaymentLogger.class);
public void logSuccess(Order order, PaymentResult result) {
logger.info("Payment processed for order {}", order.getId());
}
}
// Оркестратор: координирует работу
@Service
public class PaymentService {
@Autowired
private PaymentValidator validator;
@Autowired
private PaymentGateway gateway;
@Autowired
private OrderService orderService;
@Autowired
private NotificationService notificationService;
@Autowired
private PaymentLogger logger;
@Transactional
public void processPayment(Order order) {
validator.validate(order);
PaymentResult result = gateway.process(order);
orderService.markAsPaid(order);
notificationService.sendPaymentConfirmation(order);
logger.logSuccess(order, result);
}
}
5. Правила идентификации нарушения SRP
// ПРИЗНАК: "and" в названии класса
public class UserAndAuthenticationManager { } // ❌ Нарушает SRP
// ПРИЗНАК: "or" в описании ответственности
// "Класс отвечает за управление пользователем ИЛИ аутентификацию"
// ПРИЗНАК: Много импортов разных систем
public class UserProcessor {
private EmailService emailService; // email
private SmsService smsService; // SMS
private LogService logService; // логирование
private PaymentService payment; // платежи
// Слишком много ответственностей!
}
// ПРИЗНАК: Класс изменяется по разным причинам
// Изменение email логики → User изменяется
// Изменение логирования → User изменяется
// Изменение паролей → User изменяется
6. Тестирование с применением SRP
@SpringBootTest
public class PasswordServiceTest {
@Autowired
private PasswordService passwordService; // Тестирует только пароли
@Test
public void testPasswordHashing() {
String password = "Test123!";
String hash = passwordService.hashPassword(password);
assertTrue(passwordService.isPasswordValid(password, hash));
}
}
@SpringBootTest
public class EmailValidatorTest {
private EmailValidator validator = new EmailValidator();
@Test
public void testValidEmail() {
assertTrue(validator.isValid("user@example.com"));
assertFalse(validator.isValid("invalid.email"));
}
}
@SpringBootTest
public class UserServiceTest {
@MockBean
private UserRepository userRepository;
@MockBean
private EmailValidator emailValidator;
@MockBean
private EmailService emailService;
@Autowired
private UserService userService; // Тестируем только оркестрацию
@Test
public void testCreateUser() {
// Легко мокировать, так как зависимости разделены
}
}
Преимущества SRP
- Легче тестировать — каждый класс делает одно
- Проще поддерживать — изменения локализованы
- Переиспользование — классы можно использовать в разных контекстах
- Читаемость — код понятнее
- Меньше зависимостей — каждый класс зависит только от нужного
Антипаттерны
// God Object — делает всё
public class Application { // ❌
public void handleHttp() { }
public void processDatabase() { }
public void sendEmails() { }
public void runBusinessLogic() { }
}
// Feature Envy — класс слишком интересуется другим классом
public class OrderProcessor {
public void process(Order order, User user) {
// Много обращений к User
user.getEmail(); // ❌
user.getPhoneNumber(); // ❌
user.getAddress(); // ❌
// Лучше передать UserDTO
}
}
Вывод
SRP — это не просто принцип, это инвестиция в качество кода. Класс должен иметь одну причину для изменения, что делает код:
- Проще в тестировании
- Проще в поддержке
- Более модульным и гибким