Маленькие заметки для тех, кому сложно понять кучу умных слов,
Single Responsibility Principle (SRP) — принцип единственной ответственности
Стих:
Смысл единственной ответственности
Делай модули поменьше, плоди наследственности.
Обращение к базе данных, генерация html,
Запихнул ты в суперкласс — ты очень неумел!
Объяснение:
Каждый класс должен решать только одну задачу. Если класс занимается логированием, работой с базой данных и рендерингом HTML — это нарушение SRP.
// Плохой пример: класс делает всё
class User {
public function saveToDatabase(): void { /* ... */ }
public function generateHtmlReport(): string { /* ... */ }
}
// Хороший пример: разделение ответственности
class User {
public function getData(): array { /* ... */ }
}
class UserRepository {
public function save(User $user): void { /* ... */ }
}
class UserHtmlRenderer {
public function render(User $user): string { /* ... */ }
}
Open Closed Principle (OCP) — принцип открытости-закрытости
Стих:
Открыт для расширения, изменениям закрыт,
Этот принцип сильно, в описании размыт.
Не надо нам условий, в родителе плодить,
Добавь ему наследников, и им теперь рулить.
Объяснение:
Классы должны быть открыты для расширения (через наследование, композицию), но закрыты для модификации.
// Плохой пример: добавление новых типов требует изменения класса
class Logger {
public function log(string $type, string $message): void {
if ($type === 'file') {
// запись в файл
} elseif ($type === 'database') {
// запись в базу
}
}
}
// Хороший пример: использование абстракций
interface Logger {
public function log(string $message): void;
}
class FileLogger implements Logger {
public function log(string $message): void { /* ... */ }
}
class DatabaseLogger implements Logger {
public function log(string $message): void { /* ... */ }
}
class LogProcessor {
public function __construct(private Logger $logger) {}
public function process(string $message): void {
$this->logger->log($message);
}
}
Liskov Substitution Principle (LSP) — принцип подстановки Лисков
Стих:
Любимый принцип подстановки, Барбары Лисков,
Он на самом деле, в подходе прям суров!
Если есть наследник, то должен он уметь,
Все что и родитель, ничто не затереть.
Объяснение:
Наследники должны сохранять поведение родительского класса. Если класс Dog наследуется от Animal, то везде, где используется Animal, можно использовать Dog без сбоев.
// Нарушение LSP: класс Square меняет поведение родителя
class Rectangle {
public function setWidth(int $w): void { /* ... */ }
public function setHeight(int $h): void { /* ... */ }
}
class Square extends Rectangle {
public function setWidth(int $w): void {
$this->width = $w;
$this->height = $w; // Неожиданное поведение!
}
}
// Решение: не наследовать Square от Rectangle, если их поведение разное.
Interface Segregation Principle (ISP) — принцип разделения интерфейса
Стих:
Интерфейсики огромные, нам следует разбить,
Лучше их по коду, побольше наплодить!
Захочешь почесаться, чихнуть или зевнуть,
Отдельный интерфейс написать ты не забудь!
Объяснение:
Интерфейсы должны быть узкоспециализированными. Клиенты не должны зависеть от методов, которые они не используют.
// Плохой пример: "толстый" интерфейс
interface Worker {
public function work(): void;
public function sleep(): void;
}
class HumanWorker implements Worker {
public function work(): void { /* ... */ }
public function sleep(): void { /* ... */ }
}
class RobotWorker implements Worker {
public function work(): void { /* ... */ }
public function sleep(): void {
throw new Exception('Роботы не спят!'); // Нарушение ISP
}
}
// Хороший пример: разделение интерфейсов
interface Workable {
public function work(): void;
}
interface Sleepable {
public function sleep(): void;
}
class HumanWorker implements Workable, Sleepable { /* ... */ }
class RobotWorker implements Workable { /* ... */ }
Dependency Inversion Principle (DIP) — принцип инверсии зависимостей
Стих:
Депенденси инвершн, проще говоря,
Используйте абстракции, как понимаю я!
Передавайте в методы, какой-то внешний класс,
А в нем храним мы логику, и клево все у нас!
Объяснение:
Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.
// Плохой пример: зависимость от конкретной реализации
class PasswordReminder {
private MySQLConnection $db;
public function __construct() {
$this->db = new MySQLConnection(); // Жёсткая связь
}
}
// Хороший пример: зависимость от абстракции
interface Connection {
public function connect(): void;
}
class MySQLConnection implements Connection {
public function connect(): void { /* ... */ }
}
class PasswordReminder {
public function __construct(private Connection $db) {}
}
Интересные факты о SOLID
Акроним придумал Роберт Мартин. Термин SOLID впервые появился в его статье 2000 года, хотя сами принципы разрабатывались разными авторами (например, Барбарой Лисков).
LSP — часть теории подтипов. Принцип Лисков формализован в компьютерной науке как «контрактное проектирование».
DIP — основа современных фреймворков. Инверсия зависимостей лежит в основе IoC-контейнеров (например, в Laravel).
SRP vs. God Object. Нарушение SRP часто приводит к созданию «божественных классов», которые сложно тестировать и поддерживать.
OCP и паттерн Стратегия. Принцип открытости/закрытости часто реализуется через стратегию, декоратор или фабричный метод.
Итог: SOLID — не догма, а инструмент. Используйте его там, где он уменьшает сложность, а не создаёт её.
За пояснениями сюда: