Встречается на 40% собеседований по PHP

Расскажи о принципах SOLID

SOLID — это акроним пяти принципов объектно-ориентированного проектирования, сформулированных Робертом Мартином (дядюшкой Бобом). Эти принципы помогают создавать гибкие, понятные и легко поддерживаемые системы, снижая связанность кода и упрощая его тестирование и расширение. Каждый принцип решает конкретную проблему проектирования, а вместе они образуют основу для качественной архитектуры программного обеспечения.

Как это работает

Принципы SOLID применяются на этапе проектирования классов и модулей. Они не являются строгими правилами, а скорее рекомендациями, которые помогают избежать типичных ошибок проектирования. Рассмотрим каждый принцип подробнее.

S — Single Responsibility Principle (Принцип единственной ответственности)

Класс должен иметь только одну причину для изменения. Это означает, что класс должен выполнять только одну задачу или отвечать за одну область функциональности. Например, класс Order не должен одновременно управлять заказами и отправлять email-уведомления. Лучше разделить эти обязанности на разные классы.

O — Open/Closed Principle (Принцип открытости/закрытости)

Программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для изменения. Это значит, что новую функциональность можно добавлять без модификации существующего кода. Достигается это через наследование, полиморфизм или композицию. Например, вместо изменения метода calculateDiscount() для каждого нового типа скидки, можно создать подклассы, переопределяющие этот метод.

L — Liskov Substitution Principle (Принцип подстановки Барбары Лисков)

Объекты в программе должны быть заменяемы на экземпляры их подтипов без изменения правильности выполнения программы. Проще говоря, производный класс должен дополнять, а не заменять поведение базового класса. Если подкласс нарушает контракт базового класса (например, выбрасывает исключение там, где базовый не выбрасывает), то это нарушение принципа.

I — Interface Segregation Principle (Принцип разделения интерфейса)

Клиенты не должны быть вынуждены реализовывать интерфейсы, которые они не используют. Лучше иметь несколько специализированных интерфейсов, чем один универсальный. Например, вместо одного интерфейса Worker с методами work() и eat(), лучше разделить на Workable и Eatable, чтобы робот не был вынужден реализовывать метод eat().

D — Dependency Inversion Principle (Принцип инверсии зависимостей)

Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей; детали должны зависеть от абстракций. Это означает, что зависимости должны быть направлены на абстракции (интерфейсы или абстрактные классы), а не на конкретные реализации. Например, класс ReportGenerator должен зависеть от интерфейса ReportFormatter, а не от конкретного класса PdfFormatter.

Пример на PHP

Рассмотрим пример, иллюстрирующий принцип открытости/закрытости и инверсии зависимостей.

php
<?php

// Абстракция для форматирования отчета
interface ReportFormatter {
    public function format($data): string;
}

// Конкретная реализация для PDF
class PdfFormatter implements ReportFormatter {
    public function format($data): string {
        // логика формирования PDF
        return 'PDF: ' . $data;
    }
}

// Конкретная реализация для HTML
class HtmlFormatter implements ReportFormatter {
    public function format($data): string {
        // логика формирования HTML
        return '<p>' . $data . '</p>';
    }
}

// Класс, который генерирует отчет, зависит от абстракции
class ReportGenerator {
    private $formatter;

    public function __construct(ReportFormatter $formatter) {
        $this->formatter = $formatter;
    }

    public function generate($data): string {
        return $this->formatter->format($data);
    }
}

// Использование: можно подставить любую реализацию без изменения ReportGenerator
$generator = new ReportGenerator(new PdfFormatter());
echo $generator->generate('Данные отчета');

Здесь ReportGenerator зависит от интерфейса ReportFormatter, а не от конкретного класса. Это позволяет добавлять новые форматы (например, JsonFormatter) без изменения существующего кода, что соответствует принципу открытости/закрытости и инверсии зависимостей.

Подводные камни

  • Чрезмерное дробление: Слишком строгое следование принципу единственной ответственности может привести к созданию множества мелких классов, что усложнит понимание системы.
  • Нарушение LSP: Часто встречается, когда подкласс изменяет предусловия или постусловия базового метода, что приводит к ошибкам при замене.
  • Интерфейсы-зомби: Слишком много мелких интерфейсов может запутать разработчиков, если они не имеют четкой смысловой нагрузки.
  • Жесткость: Принцип открытости/закрытости требует продуманного проектирования с самого начала, иначе добавление новой функциональности потребует изменения существующего кода.

Когда использовать

SOLID полезен в большинстве проектов, особенно в крупных и долгоживущих. Он помогает снизить стоимость поддержки и развития. Однако для небольших прототипов или разовых скриптов следование всем принципам может быть избыточным. Важно соблюдать баланс и применять принципы там, где они действительно приносят пользу.

Коротко

  • SOLID — пять принципов проектирования: единственная ответственность, открытость/закрытость, подстановка Лисков, разделение интерфейса, инверсия зависимостей.
  • Принципы направлены на снижение связанности, упрощение тестирования и расширения кода.
  • На собеседовании важно не только перечислить принципы, но и привести примеры их применения на практике.
  • Помните о возможных перегибах: не создавайте лишние абстракции и классы без необходимости.
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы