Що таке принцип інверсії залежності і чому він важливий?


178

Що таке принцип інверсії залежності і чому він важливий?



3
Смішна кількість відповідей тут, використовуючи терміни "високого рівня" та "низького рівня" з Вікіпедії. Ці умови є недоступними і приводять багато читачів на цю сторінку. Якщо ви збираєтеся відрегулювати вікіпедію, визначте ці терміни у своїх відповідях, щоб дати контекст!
8bitjunkie

Відповіді:


107

Перевірте цей документ: Принцип інверсії залежності .

Це в основному говорить:

  • Модулі високого рівня не повинні залежати від модулів низького рівня. Обидва повинні залежати від абстракцій.
  • Абстракції ніколи не повинні залежати від деталей. Деталі повинні залежати від абстракцій.

Щодо того, чому це важливо, коротше: зміни є ризиковими, і залежно від концепції, а не від реалізації, ви зменшуєте потребу в змінах на сайтах викликів.

Ефективно, DIP зменшує зв'язок між різними фрагментами коду. Ідея полягає в тому, що, хоча існує багато способів реалізації, скажімо, лісозаготівлі, спосіб, яким ви користуєтесь ним, повинен бути відносно стабільним у часі. Якщо ви можете витягти інтерфейс, який представляє концепцію ведення журналів, цей інтерфейс повинен бути набагато стабільнішим за час, ніж його реалізація, а сайти викликів повинні бути значно менше впливати на зміни, які ви могли внести під час збереження або розширення цього механізму реєстрації.

Завдяки тому, що реалізація залежить від інтерфейсу, ви отримуєте можливість у процесі виконання вибирати, яка реалізація краще підходить для вашого конкретного середовища. Залежно від випадків, це може бути цікавим.


31
Ця відповідь не говорить про те, чому важливий DIP або навіть що таке DIP. Я прочитав офіційний документ DIP і вважаю, що це дійсно поганий і невиправданий "принцип", оскільки він заснований на хибному припущенні: що модулі високого рівня підлягають багаторазовому застосуванню.
Rogério

3
Розглянемо графік залежності для деяких об'єктів. Застосувати DIP до об'єктів. Тепер будь-який об’єкт буде залежним від реалізації інших об'єктів. Тестування одиниць тепер просте. Пізніше можливе рефакторинг для повторного використання. Зміни дизайну мають дуже обмежені сфери змін. Проблеми дизайну не каскадують. Дивіться також схему AI "Blackboard" щодо інверсії даних. Разом із цим дуже потужні інструменти, які роблять програмне забезпечення зрозумілим, бездоганним та надійним. Ігноруйте введення залежності в цьому контексті. Це не пов'язано.
Тім Вілліскрофт

6
Клас A, що використовує клас B, не означає, що A "залежить" від реалізації B; це залежить лише від публічного інтерфейсу B. Додавання окремої абстракції, від якої тепер залежать лише A і B, означає лише, що A більше не буде мати залежності часу компіляції від загальнодоступного інтерфейсу B. Ізоляція між одиницями може бути легко досягнута без цих додаткових абстракцій; для цього є спеціальні інструменти глузування для Java та .NET, які розглядають усі ситуації (статичні методи, конструктори тощо). Застосування DIP, як правило, робить програмне забезпечення більш складним і менш ремонтом, і не є більш перевіреним.
Rogério

1
Тоді що таке інверсія управління? спосіб досягти інверсії залежності?
користувач20358

2
@ Роджеріо, див. Відповідь Дерека Грера нижче, він пояснює це. AFAIK, DIP каже, що саме А мандатує те, що потрібно А, а не Б, хто говорить, що потрібно А. Отже, інтерфейс, який потребує A, не повинен давати B, а для А.
ejaenv

145

Книги Agile Development Software, Principles, Patterns, and Practices та Agile Principles, Patterns and Practices в C # - найкращі ресурси для повного розуміння початкових цілей та мотивацій, що стоять за принципом інверсії залежності. Стаття "Принцип інверсії залежності" також є хорошим ресурсом, але через те, що це узагальнена версія проекту, який врешті-решт пробився у вищезгадані книги, він залишає важливу дискусію щодо концепції володіння пакетом та інтерфейсом, які є ключовими для розрізнення цього принципу, від загальної радить "програмувати на інтерфейс, а не на реалізацію", знайдений у книзі Шаблони дизайну (Gamma, et al.).

Для надання підсумків, Принцип інверсії залежності залежить насамперед про зміну звичайного напрямку залежності від компонентів "вищого рівня" до компонентів "нижчого рівня", так що компоненти "нижчого рівня" залежать від інтерфейсів, що належать компонентам "вищого рівня". . (Примітка: компонент "вищого рівня" тут відноситься до компонента, що вимагає зовнішніх залежностей / послуг, не обов'язково його концептуальної позиції в шаруватої архітектури.) При цьому зв'язок не зменшується настільки, як він зміщений від компонентів, які теоретично менш цінний для компонентів, які теоретично є більш цінними.

Це досягається шляхом проектування компонентів, зовнішні залежності яких виражаються в інтерфейсі, для якого реалізацію повинен забезпечити споживач компонента. Іншими словами, визначені інтерфейси виражають те, що потрібно компоненту, а не як ви використовуєте компонент (наприклад, "INeedSomething", а не "IDoSomething").

Принцип інверсії залежності не посилається на просту практику абстрагування залежностей за допомогою інтерфейсів (наприклад, MyService → [ILogger ⇐ Logger]). Хоча це відокремлює компонент від конкретної деталі реалізації залежності, він не інвертує взаємозв'язок між споживачем та залежністю (наприклад, [MyService → IMyServiceLogger] ⇐ Logger.

Важливість принципу інверсії залежності залежить від єдиної мети - можливості повторно використовувати компоненти програмного забезпечення, які залежать від зовнішніх залежностей для частини їх функціональності (реєстрація, перевірка та ін.)

У рамках цієї загальної мети повторного використання ми можемо виділити два підтипи повторного використання:

  1. Використання програмного компонента в декількох додатках з реалізаціями підзалежності (наприклад, ви розробили контейнер DI і хочете забезпечити ведення журналів, але не хочете з'єднувати ваш контейнер із певним реєстратором, таким чином, щоб також використовувався кожен, хто використовує ваш контейнер використовувати обрану бібліотеку журналів).

  2. Використання компонентів програмного забезпечення в контексті, що розвивається (наприклад, ви розробили бізнес-логічні компоненти, які залишаються однаковими для декількох версій програми, де змінюються деталі реалізації).

У першому випадку повторного використання компонентів у кількох додатках, таких як бібліотека інфраструктури, мета полягає в тому, щоб забезпечити ваші споживачі основної інфраструктури, не прив'язуючи споживачів до субзалежностей вашої власної бібліотеки, оскільки для залежності від таких залежностей потрібна ваша споживачі також вимагають однакових залежностей. Це може бути проблематично, коли споживачі вашої бібліотеки вирішили використовувати іншу бібліотеку для тих же інфраструктурних потреб (наприклад, NLog проти log4net) або якщо вони вирішили використовувати більш пізню версію потрібної бібліотеки, яка не сумісна з версією потрібна вашій бібліотеці.

У другому випадку повторного використання бізнес-логічних компонентів (тобто "компонентів вищого рівня") мета полягає в тому, щоб виділити реалізацію основного домену вашої програми від мінливих потреб деталей вашої реалізації (тобто зміни / оновлення стійкості бібліотек, бібліотек обміну повідомленнями , стратегії шифрування тощо). В ідеалі, зміна деталей реалізації програми не повинно порушувати компоненти, що інкапсулюють ділову логіку програми.

Примітка. Деякі можуть заперечити, щоб цей другий випадок описати як фактичне повторне використання, мотивуючи це тим, що такі компоненти, як компоненти бізнес-логіки, що використовуються в межах однієї еволюціонуючої програми, представляють лише одне використання. Однак ідея полягає в тому, що кожна зміна деталей реалізації додатка створює новий контекст і, отже, інший випадок використання, хоча кінцеві цілі можна виділити як ізоляція проти переносимості.

Хоча дотримання принципу інверсії залежності у цьому другому випадку може принести певну користь, слід зазначити, що його значення, застосоване до сучасних мов, таких як Java та C #, значно знижується, можливо, до того, що воно не має значення. Як обговорювалося раніше, DIP передбачає повне розділення деталей реалізації на окремі пакети. Однак у випадку що розвивається додатку просто використання інтерфейсів, визначених у домені бізнесу, захистить від необхідності змінювати компоненти більш високого рівня через зміни потреб у деталях деталізації про реалізацію, навіть якщо деталі про реалізацію остаточно містяться в одному пакеті . Ця частина принципу відображає аспекти, що стосувалися мови з огляду на те, коли принцип був кодифікований (тобто C ++), які не стосуються нових мов. Це сказав:

Більш тривале обговорення цього принципу, оскільки воно стосується простого використання інтерфейсів, залежної ін'єкції та схеми розділеного інтерфейсу, можна знайти тут . Крім того, обговорення того , як цей принцип відноситься до динамічно типізованих мов , таких як JavaScript можна знайти тут .


17
Дякую. Зараз я бачу, як моя відповідь пропускає суть. Різниця між MyService → [ILogger ⇐ Logger] та [MyService → IMyServiceLogger] ⇐ Журнал тонкий, але важливий.
Патрік МакЕлхані

2
У цьому ж рядку його дуже добре пояснено тут: lostechies.com/derickbailey/2011/09/22/…
ejaenv

1
Як я хотів, щоб люди перестали використовувати лісоруби як канонічну шафу для введення залежності. Особливо у зв'язку з log4net, де його майже анти-шаблон. Це вбік, пояснення кикас!
Каспер Леон Нільсен

4
@ Casper Leon Nielsen - DIP не має нічого спільного з DI. Вони не є синонімами і не еквівалентними поняттями.
TSmith

4
@ VF1 Як зазначено в підсумковому пункті, важливість принципу інверсії залежності залежить насамперед із повторного використання. Якщо Джон випускає бібліотеку, яка має зовнішню залежність від бібліотеки журналу, а Сем бажає використовувати бібліотеку Джона, Сем приймає залежність від тимчасової реєстрації. Сам ніколи не зможе розгорнути свою програму без вибраної Джона бібліотеки. Однак, якщо Джон дотримується DIP, Сем може надати адаптер і використовувати будь-яку бібліотеку журналів, яку він обере. DIP - це не про зручність, а про з'єднання.
Дерек Грір

14

Коли ми розробляємо програмні програми, ми можемо вважати класи низького рівня класами, які реалізують основні та основні операції (доступ до диска, мережеві протоколи, ...), а класи високого рівня - класи, які інкапсулюють складну логіку (бізнес-потоки, ...).

Останні покладаються на класи низького рівня. Природним способом реалізації таких структур було б писати класи низького рівня, і як тільки ми їх маємо написати складні класи високого рівня. Оскільки класи високого рівня визначаються з точки зору інших, це здається логічним способом зробити це. Але це не гнучка конструкція. Що станеться, якщо нам потрібно замінити клас низького рівня?

Принцип інверсії залежності визначає, що:

  • Модулі високого рівня не повинні залежати від модулів низького рівня. Обидва повинні залежати від абстракцій.
  • Абстракції не повинні залежати від деталей. Деталі повинні залежати від абстракцій.

Цей принцип прагне "перевернути" загальноприйняте уявлення про те, що модулі високого рівня в програмному забезпеченні повинні залежати від модулів нижчого рівня. Тут модулі високого рівня володіють абстракціями (наприклад, визначаючи методи інтерфейсу), які реалізуються модулями нижчого рівня. Таким чином, роблячи модулі нижчого рівня залежними від модулів вищого рівня.


11

Добре застосована інверсія залежності залежить від гнучкості та стабільності на рівні всієї архітектури вашої програми. Це дозволить вашій програмі розвиватися більш безпечно та стабільно.

Традиційна багатошарова архітектура

Традиційно інтерфейс багатошарової архітектури залежав від бізнес-рівня, а це в свою чергу залежало від рівня доступу до даних.

Ви повинні розуміти шар, пакет чи бібліотеку. Подивимось, яким би був код.

У нас буде бібліотека або пакет для рівня доступу до даних.

// DataAccessLayer.dll
public class ProductDAO {

}

І ще одна логіка бізнесу бібліотеки чи пакету, що залежить від рівня доступу до даних.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Шарувата архітектура з інверсією залежності

Інверсія залежності вказує на наступне:

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

Абстракції не повинні залежати від деталей. Деталі повинні залежати від абстракцій.

Що таке модулі високого рівня та низький рівень? Мислячі модулі, такі як бібліотеки або пакети, модулем високого рівня будуть такі, від яких традиційно є залежності та низький рівень, від яких вони залежать.

Іншими словами, високий рівень модуля був би там, де викликається дія, і низький рівень, де виконується дія.

З цього принципу можна зробити розумний висновок про те, що між конкрементами не повинно бути залежності, але повинна бути залежність від абстракції. Але відповідно до прийнятого нами підходу ми можемо неправильно застосовувати залежність від інвестицій, а абстракцію.

Уявіть, що ми адаптуємо наш код наступним чином:

У нас буде бібліотека або пакет для рівня доступу до даних, який визначає абстракцію.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

І ще одна логіка бізнесу бібліотеки чи пакету, що залежить від рівня доступу до даних.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

Хоча ми залежні від абстракції, залежність між бізнесом та доступом до даних залишається однаковою.

Щоб отримати інверсію залежності, інтерфейс стійкості повинен бути визначений в модулі або пакеті, де ця логіка або домен високого рівня, а не в модулі низького рівня.

Спочатку визначте, що таке доменний шар, і абстрагування його зв'язку визначається стійкістю.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

Після того, як рівень стійкості залежить від домену, переходимо до інвертування зараз, якщо визначена залежність.

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(джерело: xurxodev.com )

Поглиблення принципу

Важливо добре засвоїти концепцію, поглиблюючи мету та переваги. Якщо ми залишимось механічно та вивчимо типовий сховище випадків, ми не зможемо визначити, де ми можемо застосувати принцип залежності.

Але чому ми інвертуємо залежність? Яка головна мета поза конкретними прикладами?

Таке зазвичай дозволяє найчастіше змінювати найбільш стійкі речі, які не залежать від менш стійких речей.

Для типу стійкості легше змінити доступ до бази даних чи технології до тієї самої бази даних, ніж логіка домену або дії, призначені для постійної комунікації. Через це залежність повертається назад, оскільки так простіше змінити стійкість, якщо ця зміна відбудеться. Таким чином нам не доведеться змінювати домен. Доменний шар є найбільш стабільним з усіх, тому він не повинен залежати ні від чого.

Але є не лише цей приклад сховища. Існує багато сценаріїв, де застосовується цей принцип, і є архітектури, засновані на цьому принципі.

Архітектури

Є архітектури, де інверсія залежності є ключовою для її визначення. У всіх доменах це найважливіше, і саме абстракції вказують на протокол зв'язку між доменом та рештою пакетів або бібліотек.

Чиста архітектура

У чистій архітектурі домен розташований в центрі, і якщо ви дивитесь у напрямку стрілок, що вказують на залежність, зрозуміло, які є найважливіші та стабільні шари. Зовнішні шари вважаються нестабільними інструментами, тому уникайте залежно від них.


(джерело: 8thlight.com )

Гексагональна архітектура

Це відбувається так само з шестикутною архітектурою, де домен також розташований у центральній частині, а порти - це абстракції зв'язку від доміно назовні. Тут знову видно, що домен є найбільш стійкою і традиційна залежність перевернута.


Я не впевнений, що це означає сказати, але це речення непослідовно: "Але згідно з підходом, який ми застосовуємо, ми можемо неправильно застосовувати залежність від інвестицій, але це абстракція". Можливо, ви хочете виправити?
Марк Амерді

9

На мій принцип, принцип інверсії залежності, як описано в офіційній статті , є дійсно помилковою спробою збільшити повторне використання модулів, які за своєю суттю є менш використаними, а також спосіб вирішити проблему мовою C ++.

Проблема в C ++ полягає в тому, що файли заголовків зазвичай містять декларації приватних полів і методів. Тому, якщо модуль високого рівня C ++ включає заголовки для модуля низького рівня, це буде залежати від фактичної реалізації деталей цього модуля. І це, очевидно, не дуже добре. Але це не проблема в сучасних мовах, які часто використовуються сьогодні.

Модулі високого рівня за своєю суттю менш багаторазові для використання, ніж модулі низького рівня, оскільки перші зазвичай більше конкретного додатка / контексту, ніж останні. Наприклад, компонент, який реалізує екран інтерфейсу користувача, є найвищим рівнем, а також дуже (повністю?) Специфічним для програми. Спроба повторно використовувати такий компонент в іншому додатку є контрпродуктивною і може призвести лише до надмірної інженерії.

Отже, створення окремої абстракції на тому самому рівні компонента A, що залежить від компонента B (який не залежить від A), можна здійснити лише в тому випадку, якщо компонент A буде дійсно корисним для повторного використання в різних програмах або контекстах. Якщо це не так, то застосування DIP було б поганим дизайном.


Навіть якщо абстракція високого рівня корисна лише в контексті цієї програми, вона може мати значення, коли ви хочете замінити реальну реалізацію заглушкою для тестування (або надати інтерфейс командного рядка додатку, зазвичай доступному в Інтернеті)
Przemek Pokrywka

3
DIP важливий для різних мов і не має нічого спільного з самим C ++. Навіть якщо ваш код високого рівня ніколи не залишить вашу програму, DIP дозволяє вам містити зміни коду, коли ви додаєте або змінюєте код нижчого рівня. Це зменшує як витрати на обслуговування, так і непередбачувані наслідки змін. DIP - концепція вищого рівня. Якщо ви цього не розумієте, вам потрібно зробити ще гуглінг.
Дірк Бестер

3
Я думаю, ви неправильно зрозуміли, що я сказав про C ++. Це була просто мотивація DIP; без сумніву, це більш загальне, ніж це. Зауважте, що в офіційній статті про DIP чітко видно, що центральною мотивацією було підтримка повторного використання модулів високого рівня, зробивши їх несприйнятливими до змін модулів низького рівня; без необхідності повторного використання, то дуже ймовірно, що це буде зайвим і надмірним. (Ви читали це? У ньому також йдеться про проблему C ++.)
Rogério

1
Ви не оглядаєте зворотну програму DIP? Тобто модулі вищого рівня по суті реалізують вашу програму. Ваша основна проблема - не використовувати його повторно, це зробити його менш залежним від впровадження модулів нижчого рівня, щоб оновлення його, щоб йти в ногу з майбутніми змінами, ізолює вашу програму від спустошення часу та нових технологій. Це не спрощує заміну WindowsOS, це робить WindowsOS менш залежним від деталей реалізації FAT / HDD, щоб нові NTFS / SSD можна було підключити до WindowsOS без жодного або невеликого впливу.
стукНрод

1
Екран користувальницького інтерфейсу, безумовно, не є модулем найвищого рівня або не повинен бути жодним додатком. Модулі вищого рівня - це ті, що містять бізнес-правила. Модуль вищого рівня також не визначається його потенціалом для повторного використання. Перевірте просте пояснення дядька Боба в цій статті: blog.cleancoder.com/uncle-bob/2016/01/04/…
humbaba

8

В основному це говорить:

Клас повинен залежати від абстракцій (наприклад, інтерфейс, абстрактні класи), а не конкретних деталей (реалізації).


Чи може бути так просто? Існують довгі статті і навіть книги, про які згадував ДерекГрір? Я насправді шукав просту відповідь, але неймовірно, якщо це буде так просто: D
Дарія. V

1
@ darius-v це не інша версія приказки "використовувати абстракції". Йдеться про те, кому належать інтерфейси. Головний каже, що клієнт (компоненти вищого рівня) повинен визначати інтерфейси, а компоненти нижчого рівня повинні їх реалізовувати.
боран

6

Хороші відповіді та хороші приклади тут вже дають інші.

Причина DIP важлива в тому, що вона забезпечує принципом ОО "слабко пов'язана конструкція".

Об'єкти у вашому програмному забезпеченні НЕ повинні потрапляти в ієрархію, де деякі об'єкти - це найвищі рівні, залежні від об'єктів низького рівня. Зміни об’єктів низького рівня потім перейдуть до об'єктів верхнього рівня, що робить програмне забезпечення дуже крихким для змін.

Ви хочете, щоб ваші об'єкти вищого рівня були дуже стабільними і не були крихкими для змін, тому вам потрібно перевернути залежності.


6
Як саме DIP робить це для коду Java чи .NET? У цих мовах, всупереч C ++, зміни впровадження модуля низького рівня не потребують змін у модулях високого рівня, які ним користуються. Тільки зміни в загальнодоступному інтерфейсі можуть прорізатися, але тоді абстракція, визначена на вищому рівні, також повинна змінитися.
Rogério

5

Набагато чіткіший спосіб заявити про принцип інверсії залежності:

Ваші модулі, які інкапсулюють складну бізнес-логіку, не повинні безпосередньо залежати від інших модулів, які інкапсулюють бізнес-логіку. Натомість вони повинні залежати лише від інтерфейсів до простих даних.

Тобто, замість того, щоб реалізувати свій клас, Logicяк це зазвичай роблять люди:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

ви повинні зробити щось на кшталт:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Dataі DataFromDependencyповинен жити в тому ж модулі Logic, що і не Dependency.

Навіщо це робити?

  1. Два модулі бізнес-логіки зараз відокремлені. Після Dependencyзмін вам не потрібно змінювати Logic.
  2. Розуміння того, що Logicробить, є набагато простішим завданням: воно діє лише на те, що схоже на ADT.
  3. Logicтепер можна легше перевірити. Тепер ви можете безпосередньо створювати інстанції Dataз фальшивими даними та передавати їх. Не потрібно макети чи складні тестові риштування.

Я не думаю, що це правильно. Якщо DataFromDependency, на який безпосередньо посилаються Dependency, знаходиться той самий модуль Logic, що і Logicмодуль, він все ще безпосередньо залежить від Dependencyмодуля під час компіляції. За поясненням дядька Боба принципу , уникаючи в цьому вся точка DIP. Швидше, щоб слідувати DIP, Dataмає бути в тому ж модулі, що і Logic, але DataFromDependencyбути в тому ж модулі, що і Dependency.
Марк Амерді

3

Інверсія управління (IoC) - це схема дизайну, коли об'єкт передає свою залежність поза рамками, а не запитує рамки для своєї залежності.

Приклад псевдокоду з використанням традиційного пошуку:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Аналогічний код за допомогою IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

Перевагами IoC є:

  • У вас немає залежності від центральної основи, тому це можна змінити за бажанням.
  • Оскільки об'єкти створюються за допомогою ін'єкцій, бажано за допомогою інтерфейсів, легко створити одиничні тести, які замінюють залежності на макетні версії.
  • Роз'єднання відключення коду.

Принцип інверсії залежності і інверсія управління є одним і тим же?
Пітер Мортенсен

3
"Інверсія" в DIP не така, як в інверсії управління. Перший - про залежності часу компіляції, а другий - про контроль рівня між методами під час виконання.
Rogério

Я відчуваю, що у версії IoC щось не вистачає. Як визначається, який об'єкт Database він визначає, звідки він походить. Я намагаюся зрозуміти, як це не просто затягує, визначаючи всі залежності до найвищого рівня в гігантській залежності від бога
Річард Тінгл

1
Як уже говорив @ Rogério, DIP - це не DI / IoC. Ця відповідь помилкова IMO
zeraDev

1

Суть інверсії залежності полягає у створенні програмного забезпечення для багаторазового використання.

Ідея полягає в тому, що замість двох фрагментів коду, що спираються один на одного, вони покладаються на якийсь абстрагований інтерфейс. Тоді ви можете повторно використовувати будь-який шматок без іншого.

Шлях цього найчастіше досягається за допомогою інверсії керуючого (IoC) контейнера, як Spring у Java. У цій моделі властивості об'єктів встановлюються через конфігурацію XML замість того, щоб об'єкти виходили і знаходили їх залежність.

Уявіть цей псевдокод ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass безпосередньо залежить як від класу Service, так і від класу ServiceLocator. Це потрібно обом, якщо ви хочете використовувати його в іншому додатку. А тепер уявіть це ...

public class MyClass
{
  public IService myService;
}

Тепер MyClass покладається на єдиний інтерфейс, інтерфейс IService. Ми дозволили контейнеру IoC фактично встановити значення цієї змінної.

Тож зараз MyClass можна легко використовувати в інших проектах, не привозячи залежність цих двох класів разом із ним.

Ще краще, вам не доведеться перетягувати залежності MyService, а також залежності цих залежностей, і ... ну, ви отримуєте ідею.


2
Ця відповідь справді не стосується DIP, а чогось іншого. Два фрагменти коду можуть покладатися на деякий абстрагований інтерфейс, а не один на одного, і все ще не слідують принципу інверсії залежності.
Rogério

1

Якщо ми можемо сприймати це як те, що працівник корпорації "високого рівня" платить за виконання своїх планів, і що ці плани здійснюються сукупним виконанням багатьох планів "низького рівня" працівника, то можна сказати це загалом жахливий план, якщо опис плану працівника високого рівня будь-яким чином поєднаний з конкретним планом будь-якого працівника нижчого рівня.

Якщо керівник вищого рівня має план "покращити час доставки" і вказує, що працівник на судноплавній лінії повинен пити каву і робити розтяжки щоранку, то цей план сильно пов'язаний і має низьку згуртованість. Але якщо в плані не згадується жоден конкретний працівник, а він фактично вимагає "суб'єкт, який може виконувати роботу, готовий до роботи", тоді план нещільно пов'язаний і згуртованіший: плани не перетинаються і легко можуть бути замінені . Підрядники, або роботи, можуть легко замінити співробітників, і план високого рівня залишається незмінним.

"Високий рівень" в принципі інверсії залежності означає "важливіший".


1
Це надзвичайно хороша аналогія. Оскільки, згідно з програмою DIC, компоненти високого рівня все ще залежать від часу виконання від низькорівневих, за цією аналогією виконання планів високого рівня залежить від планів низького рівня. Але також, як і в рамках DIC, саме плани низького рівня залежать під час збирання від планів високого рівня, але, за вашою аналогією, так склалося, що формування планів низького рівня залежить від планів високого рівня.
Марк Амерді

0

Я можу побачити, що в вищезазначених відповідях було добре пояснення. Однак я хочу надати просте пояснення простим прикладом.

Принцип інверсії залежності дозволяє програмісту усунути твердо кодовані залежності, щоб додаток було вільно пов'язане та розширюване.

Як цього досягти: за допомогою абстракції

Без інверсії залежності:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

У наведеному вище фрагменті коду адресний об'єкт жорстко закодований. Натомість, якщо ми можемо використовувати інверсію залежності та ввести об'єкт адреси, пропустивши метод конструктора або сеттера. Подивимось.

З інверсією залежності:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}

-1

Інверсія залежності: залежить від абстракцій, а не від конкрементів.

Інверсія управління: Main vs Abstraction, і як Main є клеєм систем.

DIP та IoC

Про це говорять кілька хороших постів:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/


-1

Скажімо, у нас є два класи: Engineerі Programmer:

Інженер класів залежить від класу програміста, як нижче:

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

У цьому прикладі клас Engineerмає залежність від нашого Programmerкласу. Що буде, якщо мені потрібно змінити Programmer?

Очевидно, що мені теж потрібно змінити Engineer. (Нічого собі, в цьому пункті OCPтеж порушено)

Тоді, що ми маємо, щоб почистити цей безлад? Відповідь - абстракція насправді. Абстрагуванням ми можемо зняти залежність між цими двома класами. Наприклад, я можу створити Interfaceклас для програміста, і відтепер кожен клас, який бажає використовувати Programmer, повинен використовувати його. InterfaceПотім, змінюючи клас програміста, нам не потрібно змінювати будь-які класи, які його використовували, через абстракцію, яку ми б / в.

Примітка: DependencyInjectionможе допомогти нам зробити DIPі SRPтеж.


-1

Додаючи до шквалу загально хороших відповідей, я хотів би додати крихітний зразок власного, щоб продемонструвати гарну та погану практику. І так, я не один кидати каміння!

Скажімо, ви хочете, щоб маленька програма перетворила рядок у формат base64 за допомогою консольного вводу / виводу. Ось наївний підхід:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

DIP в основному говорить, що компоненти високого рівня не повинні залежати від низькорівневої реалізації, де "рівень" - це відстань від вводу / виводу за словами Роберта К. Мартіна ("Чиста архітектура"). Але як вийти з цього затруднення? Просто зробивши центральний Енкодер залежним лише від інтерфейсів, не турбуючись про те, як вони реалізовані:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

Зауважте, що вам не потрібно торкатися GoodEncoder, щоб змінити режим вводу / виводу - цей клас задоволений інтерфейсами вводу / виводу, які він знає; будь-яка низькорівнева реалізація IReadableі IWriteableніколи її не буде турбувати.


Я не думаю, що це інверсія залежності. Зрештою, ви тут не перевернули жодної залежності; ваш читач і письменник не залежать від GoodEncoderвашого другого прикладу. Щоб створити приклад DIP, вам потрібно ввести поняття про те, що "володіє" інтерфейсами, які ви витягли тут, і, зокрема, помістити їх у той самий пакет, що і GoodEncoder, поки їх реалізації залишаються назовні.
Марк Амерді

-2

Принцип інверсії залежності (DIP) говорить про це

i) Модулі високого рівня не повинні залежати від модулів низького рівня. Обидва повинні залежати від абстракцій.

ii) Абстракції ніколи не повинні залежати від деталей. Деталі повинні залежати від абстракцій.

Приклад:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Примітка: Клас повинен залежати від абстракцій, таких як інтерфейс або абстрактні класи, а не конкретних деталей (реалізація інтерфейсу).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.