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

Ответ

В своей работе я применяю все пять принципов 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()); }
    }

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

Ответ 18+ 🔞

Э, слушай, вот сидишь ты такой, пишешь код, и вроде всё работает. Но потом приходит коллега, смотрит на твоё творение и говорит: «Чувак, тут терпения ноль ебать, как в этом разбираться?». И ты сам через полгода от своего же кода охуеешь. Чтобы такого не было, есть пять простых, блядь, правил — SOLID. Это не какая-то пиздопроебибна, а реально рабочие штуки.

  1. Принцип одной ответственности (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. Принцип открытости/закрытости (OCP). Код должен быть как хороший диван: расширяем, но не переделываем. Хочешь добавить новую фичу — не лезь с паяльником в старый код, а расширь его через интерфейсы. Ёпта, красота же.

    interface DiscountStrategy {
        double applyDiscount(double price);
    }
    class RegularDiscount implements DiscountStrategy { /*...*/ }
    class PremiumDiscount implements DiscountStrategy { /*...*/ }
    class Order {
        private DiscountStrategy discount;
        // Хочешь скидку для пидарасов шерстяных? Просто новый класс создай, этот трогать не надо.
    }
  3. Принцип подстановки Лисков (LSP). Если уж ты говоришь, что твой класс — потомок другого, то он должен быть ему настоящей заменой. А не как с квадратом и прямоугольником: вроде квадрат — это прямоугольник, а подставить его — и всё, пиздец, логика ломается. Наследование — это вам не хуй с горы, думать надо.

  4. Принцип разделения интерфейса (ISP). Не заставляй класс реализовывать то, что ему нахуй не сдалось. Лучше десять маленьких и точных интерфейсов, чем один толстый, от которого всем тошно.

    // Вместо этого монстра:
    // interface Worker { void work(); void eat(); }
    // Делаем так:
    interface Workable { void work(); }
    interface Eatable { void eat(); }
    class Human implements Workable, Eatable { /*...*/ } // Человек и пашет, и жрёт
    class Robot implements Workable { /*...*/ } // Роботу похуй на метод eat(), он его не реализует
  5. Принцип инверсии зависимостей (DIP). Это, блядь, самое важное. Верхнеуровневые модули не должны зависеть от низкоуровневых костылей. Оба должны смотреть в одну сторону — на абстракцию. Пишешь сервис заказов? Он не должен знать про MySQL или MongoDB. Он должен знать про интерфейс Database. А уж что туда подсунуть — решай потом.

    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()); }
    }

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