Что такое принципы SOLID в объектно-ориентированном программировании?

Ответ

SOLID — это пять основных принципов проектирования, которые делают код более гибким, понятным и поддерживаемым.

  1. S — Принцип единственной ответственности (Single Responsibility): Класс должен иметь только одну причину для изменения.

    // Плохо: Класс занимается и бизнес-логикой, и логированием.
    class UserService {
        void saveUser(User u) { /*...*/ }
        void logToFile(String msg) { /*...*/ } // Нарушение SRP
    }
    // Хорошо: Ответственность разделена.
    class UserService { void saveUser(User u) { /*...*/ } }
    class Logger { void log(String msg) { /*...*/ } }
  2. O — Принцип открытости/закрытости (Open/Closed): Классы должны быть открыты для расширения, но закрыты для модификации.

    interface Shape { double area(); }
    class Circle implements Shape { /*...*/ } // Новые фигуры добавляются без изменения существующего кода.
    class Square implements Shape { /*...*/ }
  3. L — Принцип подстановки Барбары Лисков (Liskov Substitution): Объекты подклассов должны быть заменяемы объектами родительского класса без изменения корректности программы.

  4. I — Принцип разделения интерфейса (Interface Segregation): Много специализированных интерфейсов лучше, чем один универсальный.

    // Плохо: Принтер вынужден реализовывать ненужный метод scan().
    interface MultiFunctionDevice { void print(); void scan(); }
    // Хорошо:
    interface Printer { void print(); }
    interface Scanner { void scan(); }
  5. D — Принцип инверсии зависимостей (Dependency Inversion): Зависимости должны строиться на абстракциях, а не на конкретных реализациях.

    // Зависим от абстракции (интерфейса), а не от конкретного класса.
    class OrderProcessor {
        private final PaymentGateway gateway; // PaymentGateway - интерфейс
        OrderProcessor(PaymentGateway gateway) { this.gateway = gateway; }
    }

Ответ 18+ 🔞

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

Ну вот смотри, первый принцип, он же S — Single Responsibility. Это как если бы ты был поваром. Ты готовишь — это твоя работа. А если ты начнёшь ещё и посуду мыть, и закупки делать, и в кассу сидеть, то ты, блядь, кончишься через неделю. Так и класс: пусть делает что-то одно, а не несёт на себе овердохуища обязанностей. Вот смотри, как не надо:

class UserService {
    void saveUser(User u) { /*...*/ }
    void logToFile(String msg) { /*...*/ } // Нарушение SRP
}

Видишь? Он и пользователя сохраняет, и в файл логи пишет. Это пиздец. А вот как надо — разделить этих уродов:

class UserService { void saveUser(User u) { /*...*/ } }
class Logger { void log(String msg) { /*...*/ } }

Каждый занимается своим делом. Красота.

Дальше, O — Open/Closed. Это вообще хитрая жопа. Суть в том, что ты не должен лезть в старый, работающий код с паяльником и костылями. Ты должен его расширять. Как будто ты купил комод, а к нему докупаешь новые ящички, не разбирая весь хуй с винтом. Вот, смотри на фигуры:

interface Shape { double area(); }
class Circle implements Shape { /*...*/ }
class Square implements Shape { /*...*/ }

Хочешь добавить треугольник? Пожалуйста, создаёшь новый класс Triangle, и тебе не нужно ковыряться в логике расчёта площади для круга или квадрата. Они закрыты для изменений, но открыты для расширения. Гениально и просто, ёпта.

L — Liskov Substitution. Принцип подстановки Лисков, ага. Звучит сложно, а на деле: если у тебя есть утка, и она крякает, то её резиновая игрушечная копия должна тоже хоть как-то крякать, а не, блядь, стрелять лазером из глаз. Если класс-наследник не может делать то же, что и родитель, не ломая логику, — это пизда, архитектурный просчёт. Просто запомни: подкласс должен быть полноценной заменой родителя, а не манда с ушами.

I — Interface Segregation. Разделение интерфейсов. Это про то, что не надо делать из интерфейса швейцарский нож на все случаи жизни. Вот представь, тебе дали прибор, у которого есть кнопки "стрелять", "летать" и "варить кофе". А ты всего лишь солдат. Зачем тебе кнопка "варить кофе"? Вот и класс-принтер не должен реализовывать метод scan(), если он не умеет сканировать.

// Плохо: старый девайс, который всё умеет, но нихуя не нужно.
interface MultiFunctionDevice { void print(); void scan(); }
// Хорошо: два отдельных, честных интерфейса.
interface Printer { void print(); }
interface Scanner { void scan(); }

Теперь простой принтер реализует только Printer и не парится. Удобно, правда?

И наконец, D — Dependency Inversion. Инверсия зависимостей, мать её. Это когда ты не привязываешься к конкретной реализации, а работаешь через абстракцию. Типа, ты не говоришь "мне нужен именно курьер Вася", ты говоришь "мне нужен любой курьер". Тогда если Вася заболеет, ты подсунешь Петю, и всё будет работать.

class OrderProcessor {
    private final PaymentGateway gateway; // PaymentGateway - интерфейс, абстракция!
    OrderProcessor(PaymentGateway gateway) { this.gateway = gateway; }
}

Классу OrderProcessor похуй, какой именно платёжный шлюз ему подсунут — PayPal, Stripe или какая-нибудь левая контора. Он знает только интерфейс. Абстракция, Карл! Это и есть сила.

Вот и весь SOLID. Соблюдаешь эти принципы — код живёт долго и счастливо, не соблюдаешь — получаешь монолитного франкенштейна, которого все боятся и ненавидят. Вывод простой: не будь мудаком, пиши нормально.