Інверсії залежностей управління уприскуванням

image

Вступ
Напевно перше питання, яке виникло у вас при погляді на заголовок, був "Шта?". Насправді я просто переклав фразу "Інверсія управління, впровадження залежності" в Google Translate китайський, а потім назад. Навіщо? Потім, що на мій погляд, це хороша ілюстрація того, що відбувається насправді. Люди навколо плутають, перекручують перекручують ці поняття. За службовим обов'язком я проводжу багато інтерв'ю, і 90% того, що я чую, коли ставлю питання про DI — чесно кажучи, відверта маячня. Я зробив пошук по Хабру і знайшов кілька статей, які намагаються розкрити цю тему, але не можу сказати, що вони мені дуже сподобалися (гаразд, гаразд, я продивився тільки три перші сторінки, каюсь). Тут же на Хабре я зустрічав в коментарях таку розшифровку IoC, як Injection of Container. Хтось серйозно припускає, що є якийсь механізм ін'єкції контейнерів, який співіснує десь поруч з DI, і, мабуть, навіть робить щось схоже. Тільки з контейнерами. Мда. Насправді зрозуміти впровадження залежності дуже просто, треба всього лише…

Дивно, але факт — ця штука зі «все...» дійсно працює! Інакше ви б тут не виявилися, не так?

Річард Фейнман був дивним оповідачем, вміла ясно і доступно пояснювати досить складні речі (подивіться хоча б, це відео). Джоел Спольски вважає, що по-справжньому розумний програміст обов'язково повинен вміти розмовляти людською мовою (а не тільки на Сі). Ну і, напевно, практично кожному відомий афоризм Альберта Ейнштейна: "Якщо ви не можете пояснити шестирічному дитині, ви самі цього не розумієте". Звичайно ж, я не збираюся порівнювати вас з шестирічними дітьми, але тим не менш постараюся розповісти про DI, IoC і ще один DI максимально просто і зрозуміло.

NB. А ви знали, що крім впровадження залежності через конструктор сетер, існує ще і третій спосіб — впровадження допомогою інтерфейсу? Хоч це й виходить за рамки даної статті, але, тримаю парі, що або ви зараз відкрили для себе щось новеньке, або принаймні опустили вже заготовлений тухлий помідор.

Інверсія управління (Inversion of Control)
Що ви робите у свій вихідний? Може бути, читаєте книги. Може бути, граєте у відеоігри. Може бути, пишете код, а може потягуєте пиво за переглядом чергового серіалу (замість того, щоб засаджувати Марс яблунями). Але що б ви не робили, весь день у вашому повному розпорядженні і тільки ви керуєте розпорядком.

Однак, на жаль, вихідні закінчуються, настає понеділок, і доводиться йти на роботу (якщо, звичайно, вона у вас є). За умовами трудового договору ви повинні бути на місці о 8 ранку. Ви працюєте до полудня. Потім у вас перерву на обід, а потім ще чотири години кипучої діяльності. Нарешті, в 17:00 ви вибираєтеся з офісу і відправляєтеся додому, де знову можете розслабитися і вмонтувати пивандрия. Відчуваєте різницю? не керуєте своїм розкладом денним, це робить дехто інший — ваш роботодавець.

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

Але в один прекрасний день в кабінет входить бос і повідомляє неприємну звістку — консоль більше не в моді, світом правлять графічні інтерфейси, а значить все треба переробити. Будучи сучасним і гнучким (мова не тільки про ваших заняттях йогою) програмістом, ви відразу беретеся за внесення змін. Для цього ви підключаєте GUI-фреймворк і пишете код обробки подій. Якщо ось ця кнопка натиснута, то треба зробити те-то і те-то. А якщо користувач змінив свій вибір у випадаючому списку, то не обійтися без ось цього і цього. Все йде добре, але тут ви розумієте, що раніше було якось по-іншому. А хто, власне, викликає ці обробники подій, які ви так старанно програмуєте? Хто взагалі визначає, куди та коли користувач натиснув? Що взагалі відбувається? Де мої шкарпетки? GUI-фреймворк виявився явно хитріше, ніж ви думали, і перехопив у вас керування потоком виконання програми.

Це і є Inversion of Control — дуже абстрактний принцип, постулирующий факт завдання потоку виконання якоїсь зовнішньої по відношенню до вас сутністю.

Поняття IoC тісно пов'язане з поняттям фреймворку. Це головна характеристика, що відрізняє його від іншого способу оформлення переиспользуемого коду бібліотеки, функції якої ви просто викликаєте своєї програми. Фреймворк ж — це зовнішній каркас, що надає заздалегідь певні точки розширення. В ці точки розширення і ви вставляєте свій код, але коли він буде викликаний визначає саме фреймворк.

В якості домашнього завдання поміркуйте, чому Джефф Сазерленд наполягає на тому, що SCRUM — це саме фреймворк, а не методологія.

Інверсія залежності (Dependency Inversion)
Це та сама буква D в абревіатурі SOLID — принцип, який говорить про те, що:
  • модулі верхніх рівнів не повинні залежати від модулів нижніх рівнів. Обидва типи модулів повинні залежати від абстракцій;
  • абстракції не повинні залежати від деталей. Деталі повинні залежати від абстракцій.
Трохи плутана формулювання, тому розглянемо наступний приклад (для прикладів я буду використовувати C#).

public class Foo {
private Bar itsBar;

public Foo() {
itsBar = new Bar();
}
}

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

public class Foo {
private IBar itsBar;

public Foo() {
itsBar = new Bar();
}
}

Діаграма UML наочно демонструє обидва варіанти.

image

Труднощі починаються, коли запитуєш, а де ж тут, власне, інверсія? Основна ідея, без розуміння якої неможливо відповісти на це питання полягає в тому, що інтерфейси належать не своїм реалізацій, а використовують їх клієнтам. Ім'я користувача IBar вводить в оману і змушує розглядати зв'язку IBar + Bar як єдине ціле. У той же час істинним власником IBar є клас Foo, і якщо прийняти це до уваги, то напрямок зв'язку між Foo Bar і дійсно звернеться назад.

image

Впровадження залежності (Dependency Injection)
Поглянувши на отриманий код, уважний читати, звичайно ж, помітить, що навіть незважаючи на введення проміжної абстракції, за инстантинацию класу Bar раніше відповідає клас Foo. Очевидно, що це не зовсім те поділ, на яке можна було розраховувати.

public class Foo {
private IServer itsServer;

public Foo() {
itsServer = new Bar();
}
}

Щоб позбавити клас Foo від такої неприємної обов'язки, добре було б винести код инстантинации кудись в інше місце і инкапсулировать його там (оскільки всі ми надзвичайно прагматичні і не любимо нічого писати двічі). Зробити це можна двома способами — за допомогою Service Locator, або Dependency Injection.

Locator Service — це такий реєстр відповідності абстракцій та їх реалізацій. Ви згодовуєте йому зацікавив вас інтерфейс, а у відповідь отримуєте готовий екземпляр конкретного класу. Виглядає це приблизно так:

public class Foo {
private IServer itsServer;

public Foo(){
itsServer = ServiceLocator.Resolve<IServer>();
}
}

Нюанс полягає в тому, що клас Foo тепер абсолютно не залежить від класу Bar, але як і раніше управляє инстантинацией. Як ми вже знаємо, уникнути цього можна инвертировав потік управління, тобто передавши оне управління в руки якогось зовнішнього механізму. Dependency Injection і є таким механізмом, реалізованим у вигляді фреймворків під назвою IoC-контейнери:

public class Foo {
private IServer itsServer;

public Foo(IServer server) {
itsServer = server;
}
}


Висновок
Насправді IoC-контейнер — настільки безглузда назва, що навскидку навіть важко придумати щось гірше. Воно абсолютно нічого не говорить про те, чим насправді займається, вводячи в оману десятки нових програмістів кожен день. Абсолютно будь-фреймворк можна назвати IoC-контейнером, так як він за визначенням реалізує інверсію управління і є оболонкою для якогось коду загального призначення. Цей термін був (і залишається) настільки огидним, що Мартін Фаулер придумав іншого — впровадження залежності.

Підсумуємо. Dependency Injection — це не синонім Inversion of Control, Dependency Inversion — не синонім Dependency Injection, а IoC-контейнери — яскравий приклад того, як можна намертво заплутати всіх за допомогою одного єдиного невдалого терміна.

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

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.