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

Ответ

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

  1. Single Responsibility (SRP) — класс должен иметь одну причину для изменения.
    // Плохо: UserManager обрабатывает и логику, и сохранение
    class UserManager {
        fun saveUser(user: User) { /*...*/ }
        fun validateUser(user: User) { /*...*/ }
    }
  1. Open-Closed (OCP) — классы открыты для расширения, но закрыты для модификации. Используйте интерфейсы/абстракции.

  2. Liskov Substitution (LSP) — подклассы должны заменять родительские классы без изменения поведения.

  3. Interface Segregation (ISP) — клиенты не должны зависеть от интерфейсов, которые они не используют.

  4. Dependency Inversion (DIP) — зависимости должны строиться на абстракциях, а не на конкретных реализациях.

Пример DIP:

interface Database {  
    fun save(data: String)  
}  

class UserRepository(private val db: Database) {  
    fun saveUser(user: User) = db.save(user.toString())  
}  

Ответ 18+ 🔞

А, блядь, SOLID! Ну это ж классика, ёпта, как борщ со сметаной. Каждый второй джуниор на собеседовании про них слышал, но как применить — нихуя не понимает. Давай разжую, как для дебила, но с любовью.

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

Смотри, как не надо делать, я прям чувствую подозрение ебать от такого кода:

// Плохо: UserManager — это пиздопроебибна на миллион строк
class UserManager {
    fun saveUser(user: User) { /*...*/ } // Сохраняет в базу
    fun validateUser(user: User) { /*...*/ } // Проверяет почту
    fun sendWelcomeEmail(user: User) { /*...*/ } // Шлёт письмо
    fun hashPassword(password: String) { /*...*/ } // Хеширует пароль
}

Этот класс — распиздяй конченый. Захотел поменять логику отправки писем — трогаешь класс, который отвечает за сохранение. В рот меня чих-пых! Всё развалится в один прекрасный день.

Второй — Open-Closed (OCP). Суть в том, что твой код должен быть как хороший диван: ты можешь накинуть на него новый чехольчик (расширить функционал), но сам диван перешивать не надо (не модифицировать существующий код). Достигается через интерфейсы и абстракции. Если ты каждый раз лезешь в работающую логику и что-то там меняешь — ты ходишь по охуенно тонкому льду, чувак.

Третий — Liskov Substitution (LSP). Звучит сложно, а на деле просто: если у тебя есть класс Утка и от него наследуется РезиноваяУтка, то везде, где в коде ожидается Утка, должна спокойно работать и РезиноваяУтка. Если твоя резиновая утка вдруг не крякает, а взрывается — это нарушение принципа, пидарас шерстяной. Подкласс не должен ломать ожидаемое поведение родителя.

Четвёртый — Interface Segregation (ISP). Не заставляй клиента реализовывать то, что ему не нужно. Это как если бы тебе для работы курьером выдали манда с ушами — полный комплект инструментов хирурга. Ты будешь таскать эту хуйню, а пользоваться только сумкой. Интерфейсы должны быть маленькими и целевыми.

Пятый — Dependency Inversion (DIP). Самый важный, пожалуй. Верхнеуровневые модули не должны зависеть от нижнеуровневых. И те, и другие должны зависеть от абстракций (интерфейсов). Проще говоря, не привязывайся к конкретной базе данных или внешнему сервису.

Вот, смотри, как это выглядит в коде. Вместо того чтобы врубать конкретную MySQL напрямую:

// Раньше было так: жёсткая привязка. Пиздец.
class UserRepository {
    private val mySqlDatabase = MySQLDatabase() // Прикинь, завтра надо на PostgreSQL перейти!

    fun saveUser(user: User) = mySqlDatabase.save(user.toString())
}

Делаем по-взрослому, с абстракцией:

// Абстракция. Это наш контракт.
interface Database {
    fun save(data: String)
}

// Конкретная реализация. Может быть MySQL, PostgreSQL, хоть файлик на диске.
class MySQLDatabase : Database {
    override fun save(data: String) { /* Реальная работа с MySQL */ }
}

// Класс-клиент. Ему похуй, что там внутри. Он знает только интерфейс.
class UserRepository(private val db: Database) { // Зависимость внедряется извне!
    fun saveUser(user: User) = db.save(user.toString())
}

Ни хуя себе, правда? Теперь UserRepository не знает про MySQL. Он знает только, что у него есть некий Database, у которого есть метод save. Захотел поменять базу — создал новый класс PostgreSQLDatabase, реализующий тот же интерфейс, и подсунул его в репозиторий. Сам репозиторий не изменился ни на строчку. Это и есть гибкость.

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

Видео-ответы