Что такое принципы SOLID?

Ответ

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

1. Принцип единственной ответственности (Single Responsibility Principle - SRP)

Класс должен иметь одну и только одну причину для изменения.

Нарушение:

class Order {
    void calculateTotal() { /* ... */ }
    void saveToDatabase() { /* ... */ } // Ответственность за персистентность
    void printInvoice() { /* ... */ }   // Ответственность за представление
}
// Изменение формата печати или логики БД затронет один класс.

Соблюдение:

class Order {
    void calculateTotal() { /* ... */ }
}
class OrderRepository {
    void save(Order order) { /* ... */ } // Работа с БД
}
class InvoicePrinter {
    void print(Order order) { /* ... */ } // Формирование печатной формы
}
// Ответственности разделены. Изменения в одной области затрагивают только один класс.

2. Принцип открытости/закрытости (Open/Closed Principle - OCP)

Программные сущности должны быть открыты для расширения, но закрыты для модификации.

Пример: Вместо изменения существующего класса-валидатора для добавления нового правила, нужно расширить его функциональность через наследование или композицию.

3. Принцип подстановки Барбары Лисков (Liskov Substitution Principle - LSP)

Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности программы.

Нарушение: Класс Square, наследующий от Rectangle, который переопределяет сеттеры ширины и высоты для изменения обоих свойств одновременно, нарушает контракт, ожидаемый от прямоугольника.

4. Принцип разделения интерфейса (Interface Segregation Principle - ISP)

Много специализированных интерфейсов лучше, чем один универсальный.

Плохо: IMachine с методами print(), scan(), fax(). Хорошо: IPrinter, IScanner, IFaxMachine. Класс SimplePrinter реализует только IPrinter.

5. Принцип инверсии зависимостей (Dependency Inversion Principle - DIP)

Зависимости должны строиться на абстракциях, а не на деталях. Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.

Плохо (прямая зависимость от конкретной реализации):

class OrderService {
    private MySQLDatabase database; // Зависимость от конкретной БД
    public OrderService() {
        this.database = new MySQLDatabase();
    }
}

Хорошо (зависимость от абстракции, внедрение через конструктор):

interface Database {
    void save(Object data);
}

class OrderService {
    private Database database; // Зависимость от абстракции
    public OrderService(Database db) { // Внедрение зависимости (DI)
        this.database = db;
    }
}
// Теперь OrderService может работать с MySQLDatabase, PostgreSQLDatabase или даже с FakeDatabase для тестов.

Следование SOLID-принципам приводит к созданию слабосвязанных (loosely coupled) компонентов, которые проще тестировать, повторно использовать и модифицировать.

Ответ 18+ 🔞

А, смотри-ка, народ собрался про SOLID поговорить! Ну что ж, давайте разложим эту, блядь, матрешку по полочкам, а то некоторые пишут код, как будто их кот по клавиатуре прохаживался, сука.

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

1. Принцип единственной ответственности (SRP) Короче, одна штука — одна работа. Не надо из класса делать швейцарский нож, который и бутерброд намажет, и гвоздь забьет, и в жопу тебе воткнётся, если что. Представь мужика, который одновременно таксист, сантехник и хирург. Ну и кто он после этого? Правильно, пиздабол.

Вот как НЕ надо:

class Order {
    void calculateTotal() { /* ... */ }
    void saveToDatabase() { /* ... */ } // Опа, а это уже не его дело, блядь!
    void printInvoice() { /* ... */ }   // И это тоже! Три работы на одного — это перебор.
}
// Захотел поменять принтер — трогай весь класс заказа. Гениально, ёпта!

А вот как — красиво и по уму:

class Order {
    void calculateTotal() { /* ... */ } // Он знает только цену. И всё.
}
class OrderRepository {
    void save(Order order) { /* ... */ } // Этот чувак — мастер по засовыванию в базу. Только это.
}
class InvoicePrinter {
    void print(Order order) { /* ... */ } // А этот — художник-оформитель. Печатает и не парится.
}
// Каждый сидит в своей песочнице и не лезет в чужую. Порядок, блядь!

2. Принцип открытости/закрытости (OCP) Суть в том, что твой код должен быть как дом с пристройкой. Хочешь новую комнату — пристраивай, но не ломай несущие стены, а то весь дом, сука, рухнет. Не лезь с кувалдой в работающий код, а расширяй его через новые классы.

3. Принцип подстановки Барбары Лисков (LSP) Это про то, что если у тебя есть класс Утка, и от него наследуется РезиноваяУтка, то она должна крякать, а не, блядь, стрелять лазером из глаз. Подставил ребёнка вместо родителя — и программа не должна охуеть и сломаться. Если ломается — ты где-то накосячил с логикой наследования, пидарас шерстяной.

Классический косяк: Сделал класс Квадрат наследником Прямоугольника, а потом переопределил сеттеры так, что меняя ширину, меняешь и высоту. А клиентский код ожидал обычный прямоугольник. Пиздец, логика полетела. Нахуй так делать.

4. Принцип разделения интерфейса (ISP) Не делай один интерфейс на все случаи жизни! Это как зайти в магазин и купить универсальное устройство «3-в-1»: принтер, сканер и кофеварка. И хуйня принтер, и хуйня сканер, и кофе говно. Лучше три отдельных, но годных вещи.

Хуёво: IMachine с методами print(), scan(), fax(). Заставь простой принтер реализовать fax() — он будет тупо возвращать null или кидать исключение. Зачем это надо? Ни хуя не зачем.

Охуенно: IPrinter, IScanner, IFaxMachine. Берешь SimplePrinter, реализуешь только IPrinter и спишь спокойно. Никакого мусора.

5. Принцип инверсии зависимостей (DIP) Вот это, блядь, принцип — основа основ гибкого кода. Не цепляйся за конкретные реализации, как кобель за ногу! Цепляйся за абстракции, за интерфейсы. Модули должны общаться через договорённости (интерфейсы), а не через сплетни (конкретные классы).

Пиздец как НЕ надо (прямая привязка, как на цепь):

class OrderService {
    private MySQLDatabase database; // Жёстко привязан к MySQL! А если завтра на Postgres?
    public OrderService() {
        this.database = new MySQLDatabase(); // Создаёт сам себя. Самоедство, блядь.
    }
}
// Тестировать такое? Да хуй там! Поднимай целую БД для тестов. Овердохуища мороки.

А вот красота (зависимость от абстракции):

interface Database {
    void save(Object data); // Вот он, договор! «Я что-то сохраню». И всё.
}

class OrderService {
    private Database database; // Мне похуй, что там внутри! Лишь бы контракт соблюдало.
    public OrderService(Database db) { // Подсунули что-то — я буду с этим работать.
        this.database = db; // Внедрение зависимости, ёбана!
    }
}
// Теперь OrderService может работать с MySQL, с PostgreSQL, а для тестов ему можно подсунуть `FakeDatabase`, который просто в память пишет. Идеально!

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