Какими принципами SOLID вы руководствуетесь в разработке?

«Какими принципами SOLID вы руководствуетесь в разработке?» — вопрос из категории ООП, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В своей работе я применяю все пять принципов SOLID для создания поддерживаемого и расширяемого кода.

  1. Принцип единственной ответственности (Single Responsibility Principle, SRP): Каждый класс должен иметь одну и только одну причину для изменения. Например, я разделяю класс, отвечающий за данные пользователя, и класс, отвечающий за отправку уведомлений.

    // Плохо: Класс делает две вещи
    class UserService {
        public void saveUser(User user) { /*...*/ }
        public void sendEmail(User user) { /*...*/ }
    }
    // Хорошо: Ответственности разделены
    class UserRepository {
        public void save(User user) { /*...*/ }
    }
    class NotificationService {
        public void sendWelcomeEmail(User user) { /*...*/ }
    }
  2. Принцип открытости/закрытости (Open/Closed Principle, OCP): Классы должны быть открыты для расширения, но закрыты для модификации. Я достигаю этого через абстракции и полиморфизм.

    interface DiscountStrategy {
        double applyDiscount(double price);
    }
    class RegularDiscount implements DiscountStrategy { /*...*/ }
    class PremiumDiscount implements DiscountStrategy { /*...*/ }
    class Order {
        private DiscountStrategy discount;
        // Добавление новой скидки не требует изменения класса Order
    }
  3. Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP): Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности программы. Наследник не должен ужесточать предусловия или ослаблять постусловия родителя.

    // Нарушение LSP: Квадрат не может заменить Прямоугольник, если у того есть сеттеры для ширины и высоты по отдельности.
    // Соблюдение LSP: Наследование используется только при истинном отношении "is-a".
  4. Принцип разделения интерфейса (Interface Segregation Principle, ISP): Много специализированных интерфейсов лучше, чем один универсальный. Клиенты не должны зависеть от методов, которые они не используют.

    // Вместо одного "толстого" интерфейса:
    // interface Worker { void work(); void eat(); }
    // Я создаю раздельные:
    interface Workable { void work(); }
    interface Eatable { void eat(); }
    class Human implements Workable, Eatable { /*...*/ }
    class Robot implements Workable { /*...*/ } // Robot не зависит от Eatable
  5. Принцип инверсии зависимостей (Dependency Inversion Principle, DIP): Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. На практике я внедряю зависимости через конструктор, используя интерфейсы.

    interface Database {
        void save(Data data);
    }
    class MySQLDatabase implements Database { /*...*/ }
    class MongoDB implements Database { /*...*/ }
    class OrderService {
        private final Database db; // Зависимость от абстракции
        public OrderService(Database db) { this.db = db; }
        public void process(Order order) { db.save(order.getData()); }
    }

Следование этим принципам помогает мне писать код, который легче тестировать, рефакторить и адаптировать к новым требованиям.