Что такое принцип инверсии зависимостей

Ответ

Принцип инверсии зависимостей (DIP) — один из SOLID-принципов, гласящий:

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

Пример:

interface Storage {  
    fun save(data: String)  
}  

class DatabaseStorage : Storage {  
    override fun save(data: String) { /* SQL запрос */ }  
}  

class UserRepository(private val storage: Storage) {  
    fun saveUser(user: User) {  
        storage.save(user.toString())  
    }  
}  

Здесь UserRepository (верхний уровень) зависит от абстракции Storage, а не от конкретной реализации DatabaseStorage (нижний уровень). Это упрощает замену хранилища (например, на SharedPreferencesStorage) без изменения кода репозитория.

Ответ 18+ 🔞

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

Смысл-то, если на пальцах, простой, как три копейки, но дохуя важный. Представь себе, чувак. Есть у тебя какой-нибудь важный модуль, типа UserRepository. Это типа царь и бог, верхний уровень, логика у него крутая. И есть у него слуга — какой-нибудь DatabaseStorage, который в базу данных всё пихает. Низший уровень, так сказать, чернорабочий.

Так вот, классический пиздец начинается, когда царь-Repository напрямую говорит: «Работай, холоп DatabaseStorage!». И привязывается к нему намертво. А потом начальство приходит и говорит: «А давайте теперь не в базу, а в файлик! Или в облако!». И ты сидишь, ебать-копать, и переписываешь всего этого царя, потому что у него в кишках намертво вшито обращение к конкретной базе. Удивление пиздец! Терпения ноль, ебать!

А принцип-то DIP тебе и кричит: «Э, бошка, думай! Не будь распиздяем!». Он говорит: пусть твой важный Repository зависит не от конкретного раба-DatabaseStorage, а от какой-нибудь абстракции. От интерфейса, от протокола, от контракта — называй как хочешь. Назовём его Storage. И в этом контракте будет написано: «Умеешь сохранять строку? Молодец, ты — Storage».

И тогда твой UserRepository становится вообще похуй, кто там будет сохранять. Ему главное — чтобы ему передали кого-то, кто умеет save(data: String) делать. А уж это будет база данных, файловая система или мартышка с блокнотом — ему абсолютно насрать.

Смотри, как это выглядит в коде, тут всё честно:

interface Storage {  
    fun save(data: String)  
}  

class DatabaseStorage : Storage {  
    override fun save(data: String) { /* SQL запрос */ }  
}  

class UserRepository(private val storage: Storage) {  
    fun saveUser(user: User) {  
        storage.save(user.toString())  
    }  
}  

Видишь эту красоту? UserRepository в конструкторе принимает не какую-то конкретную хрень, а абстракцию — Storage. И спокойно её использует. А мы снаружи, когда создаём репозиторий, можем запихнуть в него что угодно, что подходит под контракт.

Захотел базу — передал DatabaseStorage. Надоела база, захотел SharedPreferences — написал класс SharedPreferencesStorage, который тоже реализует Storage, и передал его. И ни одной строчки внутри UserRepository менять не пришлось! Он, блядь, сам от себя охуел бы от такой гибкости.

Вот и весь принцип, ебушки-воробушки. Верхние модули (логика) не пашут на нижние (детали реализации). Все они пашут на абстракции. А сами абстракции (интерфейсы) — они про «что сделать», а не про «как именно». Детали (конкретные классы) уже подстраиваются под эти абстракции.

Получается такая архитектура, где всё висит не на конкретных кирпичах, а на договорённостях. И менять одну детальку, не разваливая всю хитрую жопу системы, становится в разы проще. Волнение, ебать, сразу спадает.