Search
Write a publication
Pull to refresh

[Записки программиста] Еще раз про SOLID

Level of difficultyEasy
Reading time4 min
Views1K

Маленькие заметки для тех, кому сложно понять кучу умных слов,

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

  1. Акроним придумал Роберт Мартин. Термин SOLID впервые появился в его статье 2000 года, хотя сами принципы разрабатывались разными авторами (например, Барбарой Лисков).

  2. LSP — часть теории подтипов. Принцип Лисков формализован в компьютерной науке как «контрактное проектирование».

  3. DIP — основа современных фреймворков. Инверсия зависимостей лежит в основе IoC-контейнеров (например, в Laravel).

  4. SRP vs. God Object. Нарушение SRP часто приводит к созданию «божественных классов», которые сложно тестировать и поддерживать.

  5. OCP и паттерн Стратегия. Принцип открытости/закрытости часто реализуется через стратегию, декоратор или фабричный метод.

Итог: SOLID — не догма, а инструмент. Используйте его там, где он уменьшает сложность, а не создаёт её.


За пояснениями сюда:

Принципы SOLID в картинках
Если вы знакомы с объектно-ориентированным программированием , то наверняка слышали и о принципах SO...
habr.com

Tags:
Hubs:
Total votes 10: ↑0 and ↓10-10
Comments3

Articles