Яка різниця між моделями впорскування залежностей і локалізатором послуг?


304

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

Інжекція залежності (DI), здається, використовує конструктор або сетер для "введення" її залежностей.

Приклад використання конструкторської інжекції:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

Локатор сервісів, здається, використовує "контейнер", який з'єднує свої залежності і надає йому бар.

Приклад використання сервера-локатора:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo()
  {
    this.bar = Container.Get<IBar>();
  }

  //...
}

Оскільки наші залежності є лише самими об'єктами, ці залежності мають залежності, які мають ще більше залежностей, і так далі, і так далі. Таким чином, народилася інверсія контейнера управління (або контейнер DI). Приклади: Замок Віндзор, Нінжект, Карта структури, Весна тощо)

Але МОК / DI Контейнер виглядає точно як Locator Service. Чи називає це контейнер DI неправильним ім'ям? Чи є контейнер IOC / DI лише іншим типом пошуку послуг? Чи є нюансом у тому, що ми використовуємо контейнери DI в основному, коли у нас багато залежностей?


13
Інверсія управління означає, що "об’єкт не повинен знати, як конструювати свої залежності"?!? Цей для мене новий. Ні, насправді, це не те, що означає "інверсія контролю". Див. Martinfowler.com/bliki/InversionOfControl.html Ця стаття містить навіть посилання на етимологію цього терміна, що починається з 1980-х.
Rogério


1
Марк Семанн аргументує Локатор послуг як анти-шаблон ( blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern ). Крім того, я виявив, що діаграма (знайдена тут, stackoverflow.com/a/9503612/1977871 ) корисна для розуміння проблем DI та SL. Сподіваюсь, це допомагає.
VivekDev

Відповіді:


181

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


20
Крім того, ви можете використовувати обидва під час створення класу. Конструктор за замовчуванням може використовувати SL для отримання залежностей та передачі їх "справжньому" конструктору, який отримує ці залежності. Ви отримуєте найкраще з обох світів.
Грант Пейлін

6
Ні, ServiceLocator - це той, хто відповідає за миттєве встановлення правильної реалізації для даної залежності (плагін). Що стосується DI, відповідальний за це "контейнер" DI.
Rogério

5
@Rogerio так, але клас все ще повинен знати про сервіс-локатор ... це дві залежності. Також найчастіше я бачив делегата службового локатора до контейнера DI для пошуку, особливо для перехідних об'єктів, яким потрібна підтримка служби.
Адам Гент

2
@Adam Я не сказав, що локатор служби делегує контейнер DI. Це дві взаємовиключні схеми, як описано в "офіційній" статті . Для мене сервіс-локатор має величезну перевагу перед DI на практиці: використання контейнера DI запрошує зловживання (яке я неодноразово бачив), тоді як використання Локатора послуг - ні.
Rogério

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

93

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

Хороше порівняння: http://martinfowler.com/articles/injection.html

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


17
Але як ви поводитесь із випадком, коли вам потрібно створювати об’єкти під час виконання? Якщо ви створюєте їх вручну за допомогою "нового", ви не можете використовувати DI. Якщо ви зателефонуєте в рамку DI для допомоги, ви порушите схему. То які варіанти залишилися?
Борис

9
@Boris У мене була та сама проблема, і я вирішив вводити конкретні фабрики класу. Не дуже, але роботу виконали. Дуже хотілося б побачити гарне рішення.
Charlie Rudenstål

Пряме посилання на порівняння: martinfowler.com/articles/…
Teoman shipahi

2
@Boris Якщо мені потрібно було побудувати нові об’єкти на льоту, я б ввів абстрактну фабрику для цих об'єктів. Що було б подібним до введення локатора служби в цьому випадку, але надає конкретний, рівномірний, інтервал інтерфейсу для побудови відповідних об'єктів і робить явні залежності явними.
LivePastTheEnd

51

Локатори сервісу приховують залежності - ви не можете зрозуміти, дивлячись на об’єкт, потрапляє він у базу даних чи ні (наприклад), коли він отримує з'єднання з локатора. При введенні залежності (принаймні конструкторському введенні) залежності явні.

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

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

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


3
Я віддаю перевагу "Singletons Missed
Чарльз Грем

2
Я люблю ol 'Стіва Йегге, і заголовок цієї статті є чудовим, але я вважаю, що статтю, яку я цитував, і Мішко Гевери " Сінгтона - патологічні брехуни" ( misko.hevery.com/2008/08/17/singletons-are-pathological- брехуни ) зробити кращу справу проти конкретного диявола служби пошуку.
Джефф Стернал

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

1
With dependency injection (at least constructor injection) the dependencies are explicit.. Будь ласка, поясніть.
FindOutIslamNow

Як і вище, я не бачу, як SL робить залежності менш явними, ніж DI ...
Michał Powłoka,

38

Мартін Фаулер заявляє :

За допомогою сервера-локатора клас програми запитує його явно за допомогою повідомлення в локатор. З ін'єкцією немає явного запиту, сервіс з’являється в класі додатків - отже, інверсія управління.

Коротше кажучи: пошук локатора послуг та введення залежностей - це лише реалізація принципу інверсії залежності.

Важливим принципом є "Залежність від абстракцій, а не від конкрементів". Це зробить ваш дизайн програмного забезпечення «слабко пов'язаним», «розширюваним», «гнучким».

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

Ви можете перевірити цю посаду: Інверсія залежностей: Локатор обслуговування або Введення залежностей

Також класичний: інверсія контейнерів управління та схема вприскування залежності від Мартіна Фаулера

Проектування класів багаторазового використання Ральфа Е. Джонсона та Брайана Фута

Однак один, який відкрив мені очі, був: ASP.NET MVC: Розв’язати чи ввести? Ось питання ... Діно Еспозіто


Фантастичний підсумок: "Локатор послуг та введення залежностей - це лише реалізація принципу інверсії залежності",
Ганс

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

1
ServiceLocator та DI не мають нічого спільного з "Принципом інверсії залежності" (DIP). DIP - це спосіб підвищити багаторазовий використання компонента високого рівня, замінивши залежність часу компіляції на компонент низького рівня залежністю від абстрактного типу, визначеного разом із компонентом високого рівня, який реалізується низько- компонент рівня; таким чином залежність часу від компіляції перетворюється, оскільки зараз саме низькорівнева залежить від високорівневої. Також зауважте, що у статті Мартіна Фаулера пояснюється, що DI та IoC - це не одне і те ж.
Rogério

23

Клас, що використовує конструктор DI, вказує на споживання коду про необхідність задоволення залежностей. Якщо клас використовує SL внутрішньо для отримання таких залежностей, споживчий код не знає про залежності. На поверхні це може здатися кращим, але насправді корисно знати будь-які явні залежності. Краще з архітектурного виду. І під час тестування ви повинні знати, чи потрібен клас певних залежностей, і налаштувати SL для надання відповідних підроблених версій цих залежностей. З DI просто переходьте в підробки. Не величезна різниця, але вона є.

DI і SL можуть працювати разом. Корисно мати центральне місце для загальних залежностей (наприклад, налаштування, реєстратор тощо). Даний клас, що використовує подібні deps, ви можете створити "справжній" конструктор, який приймає deps, і конструктор за замовчуванням (без параметра), який витягується з SL і пересилається до "реального" конструктора.

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


Цікаво! Я використовую і DI, і SL, але не з двома конструкторами. Три або чотири нудні, найчастіше необхідні залежності (налаштування тощо) отримують із SL в режимі польоту. Все інше вводиться конструктором. Це трохи некрасиво, але прагматично.
maaartinus

10

Вони обидва - це методи впровадження IoC. Існують також інші зразки, які реалізують Інверсію управління:

  • Фабричний візерунок
  • Локатор обслуговування
  • Контейнер DI (IoC)
  • Впорскування в залежності (введення конструктора, введення параметрів (якщо не потрібно), введення інтерфейсного введення інтерфейсу) ...

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

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


7

У своєму останньому проекті я використовую і те, і інше. Я використовую ін'єкцію залежності для перевірки одиниці. Я використовую сервіс-локатор, щоб приховати реалізацію та залежність від свого контейнера IoC. і ТАК! Після використання одного з контейнерів IoC (Unity, Ninject, Windsor Castle) ви залежите від нього. І як тільки воно застаріло або з якоїсь причини, якщо ви захочете його поміняти, вам / може знадобитися змінити свою реалізацію - принаймні, корінь складу. Але сервіс-локатор конспектує цю фазу.

Як би ви не залежали від вашого контейнера IoC? Або вам потрібно буде обернути його самостійно (що погана ідея), або ви використовуєте службу Locator для налаштування свого контейнера IoC. Таким чином, ви скажете локатору служби отримати потрібний інтерфейс, і він зателефонує до контейнера IoC, налаштованого для отримання цього інтерфейсу.

У моєму випадку я використовую ServiceLocator, який є базовим компонентом. І використовуйте Unity для контейнера IoC. Якщо в майбутньому мені потрібно поміняти мій контейнер IoC на Ninject, все, що мені потрібно зробити, - це мені потрібно налаштувати свій сервіс-локатор для використання Ninject замість Unity. Легка міграція.

Ось чудова стаття пояснює цей сценарій; http://www.johandekoning.nl/index.php/2013/03/03/dont-wrap-your-ioc-container/


Посилання на статтю johandekoning порушено.
JakeJ

6

Я думаю, що вони працюють разом.

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

Роль локатора сервісу полягає в тому, щоб об'єднати вашу реалізацію. Ви налаштовуєте сервіс-локатор через деяку обв'язку завантаження на початку програми. Запуск завантаження - це процес асоціації типу реалізації до певного конспекту / інтерфейсу. Що створюється для вас під час виконання. (на основі налаштувань або завантажувальної програми). Якщо ви не застосували введення залежності, було б дуже важко використовувати локатор служби або контейнер IOC.


6

Однією з причин додати, натхненний оновленням документації, яку ми писали для проекту MEF минулого тижня (я допомагаю створити MEF).

Після того, як додаток складається з потенційно тисяч компонентів, може бути важко визначити, чи можна якийсь конкретний компонент правильно встановити. Під «правильним примірником» я маю на увазі, що в цьому прикладі, що базується на Fooкомпоненті, екземпляр IBarі буде доступний, і що компонент, що його надає:

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

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

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

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

Контейнер IoC при обережному використанні може статично проаналізувати конфігурацію виконання програми, не створюючи фактично жодних примірників залучених компонентів. Багато популярних контейнерів пропонують певну зміну цього; Microsoft.Composition , що є версією націлювання на MEFCompositionAssert . Використовуючи його, ви можете писати код на зразок:

 // Whatever you use at runtime to configure the container
var container = CreateContainer();

CompositionAssert.CanExportSingle<Foo>(container);

(Побачити цей приклад ).

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

Сподіваюся, це цікаве доповнення до цього інакше вичерпного набору відповідей на тему!


5

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

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

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

Слід зазначити, що ви можете використовувати об’єкт локатора обслуговування всередині конструктора DI, але ви не використовуєте "Шаблон локатора обслуговування". Менш заплутаним є те, якщо хтось називає його об'єктом контейнера IoC, як ви, мабуть, здогадалися, що вони по суті роблять те саме (зробіть мене виправте, якщо я помиляюся).

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

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


4

У цьому спрощеному випадку різниці немає, і їх можна використовувати взаємозамінно. Однак проблеми з реальним світом не такі прості. Просто припустимо, що у самого класу Bar була інша залежність на ім'я D. У такому випадку ваш локатор служби не зміг би вирішити цю залежність, і вам доведеться інстанціювати її в класі D; тому що обов'язок ваших класів полягає у формуванні їх залежностей. Навіть погіршиться, якби сам клас D мав інші залежності, а в реальних ситуаціях він, як правило, стає ще складнішим. У таких сценаріях DI є кращим рішенням, ніж ServiceLocator.


4
Хм, я не погоджуюся: пошук локатора сервісу, колишній. чітко видно, що там все ще існує залежність ... сервіс-локатор. Якщо сам barклас має залежність, то barвін також матиме локатор обслуговування, у цьому вся суть використання DI / IoC.
GFoley83

2

Яка різниця (якщо така є) між впорскуванням залежностей та локатором обслуговування? Обидві моделі добре застосовують принцип інверсії залежності. Шаблон "Локатор послуг" простіший у використанні в існуючій кодовій базі, оскільки він робить загальний дизайн слабкішим, не примушуючи змін до публічного інтерфейсу. З цієї ж причини код, який базується на шаблоні локатора послуг, є менш читабельним, ніж еквівалентний код, який базується на введенні залежності.

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


1

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

  • Локатор послуг використовується у споживача, і він витягує послуги за ідентифікатором з деякого сховища за прямим запитом споживача

  • Контейнер DI розташований десь на вулиці, і він приймає послуги з певного сховища та передає їх споживачеві (незалежно від конструктора чи методу)

Однак ми можемо говорити про різницю між ними лише в контексті конкретного використання споживачів. Коли Locator служби та контейнер DI використовуються в кореневому складі, вони майже схожі.


0

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


-2

Для запису

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

Якщо вам дійсно не потрібен інтерфейс (інтерфейс використовується більш ніж одним класом), НЕ МОЖЕТЕ ВИКОРИСТОВУВАТИ його . У цьому випадку IBar дозволяє використовувати будь-який клас обслуговування, який його реалізує. Однак зазвичай цей інтерфейс буде використовуватися одним класом.

Чому погано використовувати інтерфейс ?. Тому що це дуже важко налагодити.

Наприклад, скажімо, що примірник "bar" не вдався, питання: який клас не вдався ?. Який код я повинен виправити? Простий погляд, він веде до інтерфейсу, і саме тут закінчується моя дорога.

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

//Foo Needs an IBar
public class Foo
{
  private BarService bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

Якщо "бар" не вдається, я повинен перевірити і запустити клас BarService.


1
Клас - це креслення для побудови конкретного об'єкта. З іншого боку, інтерфейс є contractі просто визначає поведінку, а не дію. Замість того, щоб обходити фактичний об'єкт, спільним є лише інтерфейс, щоб споживач не мав доступу до решти вашого об'єкта. Також для одиничного тестування допомагає протестувати лише ту частину, яку потрібно перевірити. Я гадаю, що вчасно ви зрозумієте його корисність.
Гунхань
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.