Что значит буква S в SOLID?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Что значит буква S в SOLID?
Буква S в SOLID обозначает Single Responsibility Principle (Принцип единственной ответственности). Это один из самых важных и часто использующихся принципов чистого кода.
Определение
SRP гласит: Класс должен иметь одну и только одну причину для изменения. Другими словами, класс должен отвечать за одну область функциональности.
Каждый класс должен иметь одну зону ответственности и полностью инкапсулировать эту ответственность.
Плохой пример (нарушение SRP)
class User {
private string $name;
private string $email;
private string $password;
public function register(string $name, string $email, string $password): void {
// Валидация
if (strlen($password) < 8) {
throw new Exception('Password too short');
}
// Сохранение в БД
$pdo = new PDO('mysql:host=localhost;dbname=app', 'user', 'pass');
$stmt = $pdo->prepare('INSERT INTO users (name, email, password) VALUES (?, ?, ?)');
$stmt->execute([$name, $email, password_hash($password, PASSWORD_BCRYPT)]);
// Отправка email
mail($email, 'Welcome!', 'Thank you for registration');
// Логирование
file_put_contents('logs/registrations.log', date('Y-m-d H:i:s') . ' ' . $email . PHP_EOL, FILE_APPEND);
// Отправка SMS
$curl = curl_init('https://sms-api.com/send');
curl_setopt($curl, CURLOPT_POSTFIELDS, ['phone' => '...', 'message' => '...']);
curl_exec($curl);
$this->name = $name;
$this->email = $email;
$this->password = $password_hash($password, PASSWORD_BCRYPT);
}
}
Проблемы:
- Класс отвечает за множество вещей: валидация, БД, email, SMS, логирование
- Нельзя менять логику отправки email без риска сломать регистрацию
- Тестировать такой класс невозможно без mock БД, email, SMS сервисов
- Код переиспользовать сложно
Хороший пример (соблюдение SRP)
// User отвечает только за данные пользователя
class User {
public function __construct(
private string $name,
private string $email,
private string $password
) {}
public function getName(): string {
return $this->name;
}
public function getEmail(): string {
return $this->email;
}
public function getPassword(): string {
return $this->password;
}
}
// Репозиторий отвечает за сохранение в БД
class UserRepository {
public function __construct(private PDO $pdo) {}
public function save(User $user): int {
$stmt = $this->pdo->prepare(
'INSERT INTO users (name, email, password) VALUES (?, ?, ?)'
);
$stmt->execute([
$user->getName(),
$user->getEmail(),
password_hash($user->getPassword(), PASSWORD_BCRYPT)
]);
return $this->pdo->lastInsertId();
}
}
// Валидатор отвечает за проверку данных
class UserValidator {
public function validate(string $name, string $email, string $password): array {
$errors = [];
if (empty($name)) {
$errors[] = 'Name is required';
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Invalid email';
}
if (strlen($password) < 8) {
$errors[] = 'Password must be at least 8 characters';
}
return $errors;
}
}
// Сервис уведомлений отвечает за отправку
class NotificationService {
public function __construct(
private EmailService $emailService,
private SmsService $smsService,
private LoggerInterface $logger
) {}
public function notifyNewUser(User $user): void {
$this->emailService->send(
$user->getEmail(),
'Welcome!',
'Thank you for registration'
);
$this->smsService->send(
$user->getPhone(),
'Welcome to our service!'
);
$this->logger->info('New user registered', [
'email' => $user->getEmail()
]);
}
}
// Главный сервис регистрации
class UserRegistrationService {
public function __construct(
private UserValidator $validator,
private UserRepository $repository,
private NotificationService $notifier
) {}
public function register(string $name, string $email, string $password): User {
// Валидируем
$errors = $this->validator->validate($name, $email, $password);
if (!empty($errors)) {
throw new ValidationException($errors);
}
// Создаем пользователя
$user = new User($name, $email, $password);
// Сохраняем в БД
$this->repository->save($user);
// Уведомляем
$this->notifier->notifyNewUser($user);
return $user;
}
}
Преимущества:
- Каждый класс отвечает за ОДНУ вещь
- Легко менять способ отправки email без влияния на остальное
- Легко тестировать каждый компонент в изолированности
- Легко переиспользовать классы в других местах
- Если email сломается, это не влияет на логику регистрации
Как определить нарушение SRP?
Если вы слышите такие фразы, значит нарушен SRP:
-
"У класса слишком много причин для изменения"
- Меняются требования к валидации → нужно менять класс - Меняется способ сохранения в БД → нужно менять класс - Меняется способ отправки email → нужно менять класс -
"Сложно тестировать, много зависимостей"
- Нужно мокировать БД, email, SMS, логгер
- Трудно изолировать логику
-
"Класс содержит слова 'и', 'а', 'также'"
// ❌ Плохое имя - несколько ответственностей class UserManagerAndValidator {} // ✅ Хорошие имена - одна ответственность class UserManager {} class UserValidator {}
SRP в Laravel
Лучший пример в Laravel - разделение слоев:
// Model отвечает за данные
class User extends Model {}
// Repository отвечает за запросы к БД
class UserRepository {
public function findById(int $id): ?User {}
public function save(User $user): void {}
}
// Service отвечает за бизнес-логику
class UserService {
public function registerUser(string $email, string $password): User {}
}
// Controller отвечает за HTTP запросы
class UserController {
public function register(Request $request, UserService $service) {}
}
// Request отвечает за валидацию
class RegisterUserRequest extends FormRequest {
public function rules() {}
}
Каждый слой - одна ответственность!
Итог
Single Responsibility Principle - это основа чистого, тестируемого и поддерживаемого кода.
Когда каждый класс отвечает за одну вещь:
- Код легче понять
- Код легче тестировать
- Код легче изменять
- Код легче переиспользовать
- Меньше bugs благодаря низкой связанности
Это не означает, что класс должен иметь ровно один метод. Это означает, что все методы должны служить одной цели, одной ответственности.