И така, какво общо има това с IoC контейнер? Инверсия на контролен контейнер, или накратко IoC контейнер, подпомага инжектирането на зависимости и по същество изпълнява три роли. Тези роли често се разделят между контейнер и инжектор, но за простота ще се обърна към тях, сякаш са едно и също. Нека да разгледаме всяка роля.

Регистър на правила за зависимост

В нашия казус TweetStream знае, че се нуждае от услуга, за да говори с Twitter. В зависимост от внедряването на зависимостта, TweetStream може да поиска тази зависимост по различни начини. В JavaScript, където не съществуват статично въвеждане, интерфейси и базирани на език анотации, зависимостта обикновено се изисква от име под формата на низ. Например TweetStream може да посочи, че се нуждае от „twitterService“ или „twitter“ или „TheTwits“ – каквото име изглежда разумно. Важната част е, че някой участник в системата трябва да съпостави това име с правило за това как да създаде или извлече зависимостта.

Това е мястото, където се намесва IoC контейнерът. IoC контейнерът може да има правила като следните:

  • Когато се поиска „twitter“, осигурете нов екземпляр с помощта на конструктора TwitterService.
  • Когато се поиска „чат“, предоставете всичко, което се връща от ChatFactory.
  • Когато се изисква „регистратор“, осигурете единичен екземпляр на AsyncLogger.
  • Когато се поиска „диалог“, предоставете този конкретен обект.
  • Когато се изисква „ранг“, посочете стойността 1337.

Инжектор

Сега с дефинираните ни правила можем да ги използваме за създаване или извличане на зависимост. Да кажем, че нашият обект за получаване на зависимости – който ще наречем наш инжектиран – е предоставен на IoC контейнера за инжектиране на зависимости. Да кажем, например, че инжектираният поиска зависимости „чат“ и „диалог“. Контейнерът IoC търси правилото, нанесено на „чат“, стартира ChatFactory, което се случва да създаде обект на чат и го конфигурира, така че всичко да е готово за работа, след което инжектира обекта за чат в инжектирания. След това контейнерът преглежда правилото за „диалог“ и, за бога, когато правилото е било конфигурирано в IoC контейнера, е предоставен конкретен обект на IoC контейнера, който винаги трябва да се използва, когато се изисква зависимостта „диалог“ . След това IoC контейнерът предоставя този специфичен обект на инжектирания. Ако друг инжектиран по-късно поиска зависимостта „диалог“, същият обект ще бъде предоставен и на този инжектиран. Както можете да видите, правилата предлагат доста голяма гъвкавост.

В обобщение, инжекторът използва правила, за да създава или извлича зависимости и да ги инжектира в обекти, които ги изискват.

Регистър на обект на зависимост и стойност

В нашите първи две правила ние използваме конструктор и фабрика, за да създадем обекти на зависимост в момента, в който са извикани:

  • Когато се поиска „twitter“, осигурете нов екземпляр с помощта на конструктора TwitterService.
  • Когато се поиска „чат“, предоставете всичко, което се връща от ChatFactory.

От друга страна, някои правила изискват IoC контейнерът да поддържа обекти или стойности на зависимости, така че те да могат да бъдат предоставени като зависимости на инжектираните в по-късен момент, вместо да се създават отново всеки път, когато бъдат поискани. Последните три правила, които изброих, се отнасят тук:

  • Когато се изисква „регистратор“, осигурете единичен екземпляр на AsyncLogger
  • Когато се поиска „диалог“, предоставете този конкретен обект.
  • Когато се изисква „ранг“, посочете стойността 1337.

Във всеки от тях контейнерът държи обекти или стойности, така че те могат да бъдат предоставени като зависимости на инжектираните в по-късен момент. По този начин IoC контейнерът се превръща в регистър на обекти и стойности.

Някак промъквам това тук, но в случай на правилото, предоставящо екземпляр сингълтън, можете да избегнете неволите на самоналагащите се сингълтони и да пожънете предимствата на базираните на контекст сингълтони. О, чудесата на IoC контейнерите! Вижте тази публикация от Джоел Хукс за повече информация.

По-малко махане с ръка, повече код

Накратко, напоследък си играх с инжектирането на зависимости в JavaScript. Това все още е сравнително нова територия в този наш свят на JavaScript, въпреки че става все по-популярен до голяма степен благодарение на AngularJS, който осигурява инжектиране на зависимости от кутията. DeftJS също обединява инжектиране на зависимости в рамки като Ext JS и Sencha Touch.

Докато се забавлявах преди около година, почувствах желание да сготвя изразителен, свободен от зависимости, лек IoC контейнер, който нарекох Injector.js, който можете да намерите в GitHub. Въпреки че кодът може да се окаже полезен за някого, аз го споменавам тук само с надеждата, че примерите в „readme“ и „unit tests“ могат да помогнат за затвърждаване на някои от концепциите, които обсъждах тук.

Според моя опит IoC контейнерите стават най-мощни, когато са интегрирани с рамка на приложение, която може автоматично да инжектира зависимости, като същевременно изисква възможно най-малко шаблони от разработчика. Въпреки че това не е предназначено да бъде търговско представяне, AngularJS има добре интегрирано инжектиране на зависимости и препоръчвам да се запознаете с неговия работен процес, дори само за възможността за обучение, която предоставя.

Повече четене и гледане

Вижте тази фантастична презентация от Miško Hevery за това защо инжектирането на зависимости е толкова важно за чист и тестван код. Сериозно, не го пропускайте.

Ако сте човек, който чете, вижте „тази статия от Мартин Фаулър“ за по-формално и авторитетно описание на инверсия на контрол и инжектиране на зависимост.