По-перше, я хочу пояснити припущення, яке я висловлюю за цю відповідь. Це не завжди так, але досить часто:
Інтерфейси - прикметники; класи - це іменники.
(Насправді, є і інтерфейси, які є іменниками, але я хочу тут узагальнити.)
Так, наприклад, інтерфейс може бути чимось таким, як IDisposable
, IEnumerable
або IPrintable
. Клас - це реальна реалізація одного або декількох цих інтерфейсів: List
або вони Map
можуть бути реалізацією IEnumerable
.
Щоб зрозуміти: Часто ваші заняття залежать один від одного. Наприклад, у вас може бути Database
клас, який має доступ до вашої бази даних (так, сюрприз! ;-)), але ви також хочете, щоб цей клас робив журнал щодо доступу до бази даних. Припустимо, у вас є інший клас Logger
, то Database
має залежність від Logger
.
Все йде нормально.
Ви можете змоделювати цю залежність у своєму Database
класі за допомогою наступного рядка:
var logger = new Logger();
і все добре. Це добре до дня, коли ви зрозумієте, що вам потрібна купа реєстраторів: Іноді ви хочете увійти до консолі, іноді до файлової системи, іноді за допомогою TCP / IP та віддаленого сервера реєстрації і так далі ...
І звичайно, ви НЕ хочете змінювати весь код (тим часом у вас є gazillions) і замінювати всі рядки
var logger = new Logger();
автор:
var logger = new TcpLogger();
По-перше, це не весело. По-друге, це схильність до помилок. По-третє, це дурна, повторювана робота для навченої мавпи. Так, що ти робиш?
Очевидно, що це дуже гарна ідея ввести інтерфейс ICanLog
(або подібний), який реалізується всіма різними реєстраторами. Отже, крок 1 у вашому коді полягає в тому, що ви робите:
ICanLog logger = new Logger();
Тепер умовивід типу більше не змінює тип, у вас завжди є один єдиний інтерфейс, на який потрібно розробитись. Наступним кроком є те, що ви не хочете мати new Logger()
знову і знову. Таким чином, ви покладете надійність для створення нових примірників до одного, центрального фабричного класу, і ви отримаєте такий код, як:
ICanLog logger = LoggerFactory.Create();
Фабрика сама вирішує, який саме лісоруб створити. Ваш код більше не переймається, і якщо ви хочете змінити тип використовуваного реєстратора, ви його зміните один раз : Всередині заводу.
Тепер, звичайно, ви можете узагальнити цю фабрику і змусити її працювати для будь-якого типу:
ICanLog logger = TypeFactory.Create<ICanLog>();
Десь цей TypeFactory потребує даних конфігурації, фактичний клас для інстанціювання, коли запитується певний тип інтерфейсу, тому вам потрібне відображення. Звичайно, ви можете зробити це зіставлення всередині коду, але тоді зміна типу означає перекомпіляцію. Але ви також можете помістити це відображення у XML-файл, наприклад. Це дозволяє змінити фактично використаний клас навіть після часу компіляції (!), Що означає динамічно, без перекомпіляції!
Щоб надати вам корисний приклад для цього: Подумайте про програмне забезпечення, яке не входить у систему, але коли ваш клієнт дзвонить і просить про допомогу, оскільки у нього є проблеми, все, що ви надсилаєте йому, - це оновлений файл конфігурації XML, і тепер він має журнал увімкнено, і ваша підтримка може використовувати файли журналів, щоб допомогти вашому клієнту.
А тепер, коли ви трохи замінюєте імена, ви закінчуєте просту реалізацію Локатора сервісів , що є однією з двох моделей для інверсії управління (оскільки ви інвертуєте контроль над тим, хто вирішує, який саме клас інстанціювати).
Загалом, це зменшує залежність у вашому коді, але тепер весь ваш код має залежність від центрального, єдиного локатора служби.
Наступний крок у цьому рядку тепер введення залежної залежності : Просто позбудьтеся від цієї єдиної залежності від локатора сервісу: Замість того, щоб різні класи просили у локатора сервісу реалізувати певний інтерфейс, ви - ще раз - повертаєте контроль над тим, хто створює .
Що Database
стосується ін'єкцій залежностей, ваш клас тепер має конструктор, який вимагає параметра типу ICanLog
:
public Database(ICanLog logger) { ... }
Тепер у вашій базі даних завжди використовується реєстратор, але він більше не знає, звідки цей логгер.
І ось тут починає діяти рамка DI: Ви знову налаштовуєте свої зіставлення, а потім просите рамки DI, щоб вони запровадили вашу програму. Оскільки Application
клас вимагає ICanPersistData
реалізації, Database
вводиться екземпляр, але для цього він повинен спершу створити екземпляр типу реєстратора, для якого налаштовано ICanLog
. І так далі ...
Отже, коротко кажучи: введення залежності - це один із двох способів усунення залежностей у вашому коді. Це дуже корисно для зміни конфігурації після закінчення часу компіляції, і це чудова річ для тестування одиниць (оскільки це робить дуже легко вводити заглушки та / або макети).
На практиці є речі, без яких не можна обійтися службовим локатором (наприклад, якщо ви не знаєте заздалегідь, скільки екземплярів вам потрібен для певного інтерфейсу: рамка DI завжди вводить лише один екземпляр на параметр, але ви можете зателефонувати звичайно, локатор служби всередині циклу), тому найчастіше кожна структура DI також надає сервіс-локатор.
Але в основному, це все.
PS: Те, що я описав тут, - це техніка, яка називається конструкторною ін'єкцією , є також властивість введення властивостей, де не параметри конструктора, але властивості використовуються для визначення та вирішення залежностей. Розгляньте введення властивостей як необов'язкової залежності, а введення конструктора - як обов'язкові залежності. Але обговорення цього питання виходить за межі цього питання.