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

Что значит буква S в SOLID?

1.0 Junior🔥 271 комментариев
#Архитектура и паттерны#ООП

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

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

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

# Что значит буква 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:

  1. "У класса слишком много причин для изменения"

    - Меняются требования к валидации → нужно менять класс
    - Меняется способ сохранения в БД → нужно менять класс
    - Меняется способ отправки email → нужно менять класс
    
  2. "Сложно тестировать, много зависимостей"

    • Нужно мокировать БД, email, SMS, логгер
    • Трудно изолировать логику
  3. "Класс содержит слова 'и', 'а', 'также'"

    // ❌ Плохое имя - несколько ответственностей
    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 благодаря низкой связанности

Это не означает, что класс должен иметь ровно один метод. Это означает, что все методы должны служить одной цели, одной ответственности.