Чи є ServiceLocator антитілом?


137

Нещодавно я прочитав статтю Марка Семана про антидіапазон службового пошуку.

Автор вказує на дві основні причини, по яких ServiceLocator є антидіаграмою:

  1. Проблема використання API (з якою я прекрасно вживаюсь)
    Коли клас використовує сервіс-локатор, дуже важко помітити його залежності, оскільки, в більшості випадків, клас має лише один конструктор PARAMETERLESS. На відміну від ServiceLocator, підхід DI явно виставляє залежності за допомогою параметрів конструктора, тому залежності легко помітити в IntelliSense.

  2. Проблема з обслуговування (що мене
    спантеличує ) Розглянемо наступний приклад

У нас є клас "MyType", який використовує підхід до локатора сервісів :

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
    }
}

Тепер ми хочемо додати ще одну залежність до класу "MyType"

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

І ось тут починається моє непорозуміння. Автор каже:

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

Але зачекайте секунду, якби ми використовували підхід DI, ми запровадили б залежність від іншого параметра в конструкторі (у випадку введення конструктора). І проблема все одно буде. Якщо ми можемо забути налаштувати ServiceLocator, то ми можемо забути додати нове відображення в наш контейнер IoC, і підхід DI матиме таку ж проблему під час виконання.

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

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

ОНОВЛЕННЯ (РЕЗЮМЕ):

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

І ось основні відмінності, які переконували мене не використовувати Локатор послуг для моїх нових проектів:

  • Найбільш очевидне та найважливіше: Локатор послуг приховує залежність класів
  • Якщо ви використовуєте якийсь контейнер IoC, він, швидше за все, сканує весь конструктор при запуску, щоб перевірити всі залежності і дасть вам негайний зворотній зв’язок про відсутні відмітки (або неправильну конфігурацію); це неможливо, якщо ви використовуєте контейнер IoC в якості сервера пошуку

Детальніше читайте чудові відповіді, які наведені нижче.


"Хіба нам не потрібно буде оновлювати всі тести, які створювали цей клас?" Не обов'язково правда, якщо ви використовуєте конструктор у своїх тестах. У цьому випадку вам доведеться лише оновити програму.
Пітер Карлссон

Ви маєте рацію, це залежить. Наприклад, у великих програмах Android, поки що люди дуже неохоче користуються DI через проблеми з продуктивністю на мобільних пристроях з низьким рівнем спектру. У таких випадках вам потрібно знайти альтернативу, щоб все-таки писати тестовий код, і я б сказав, що "Локатор послуг" - це достатньо хороший замінник у цьому випадку. (Примітка: для Android все може змінитися, коли нова рамка Dagger 2.0 DI досить дозріла.)
Г. Ломбард

1
Зауважте, що оскільки це запитання було розміщено, на Locator-службі Марка Семана є оновлення - це анти-шаблонний опис, який описує, як локатор служби порушує OOP шляхом порушення інкапсуляції, що є його найкращим аргументом досі (і основна причина всіх симптомів що він використовував у всіх попередніх аргументах). Оновлення 2015-10-26: Основна проблема служби Locator полягає в тому, що він порушує інкапсуляцію .
NightOwl888

Відповіді:


125

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

Натомість ми маємо шукати, чи є дійсні звичаї зразків, а для локатора послуг є кілька випадків використання. Але для початку поглянемо на приклади, які ви навели.

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

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

var myType = new MyType();
myType.MyMethod();

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

public class MyType
{
    public MyType(IDep1 dep1, IDep2 dep2)
    {
    }

    public void MyMethod()
    {
        dep1.DoSomething();

        // new dependency
        dep2.DoSomething();
    }
}

Ви можете безпосередньо помітити залежності і не можете використовувати класи перед їх задоволенням.

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

Чи є візерунок анти-візерунком?

Немає.

Наприклад, інверсія керуючих контейнерів не буде працювати без сервісного розташування. Це те, як вони вирішують послуги внутрішньо.

Але набагато кращим прикладом є ASP.NET MVC та WebApi. Як ви думаєте, що робить можливим введення залежності в контролери? Правильно - сервісне розташування.

Ваші запитання

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

Є ще дві серйозні проблеми:

  1. З розташуванням служби ви також додаєте іншу залежність: сервіс-локатор.
  2. Як ви можете сказати, яке життя повинно мати залежність, і як / коли вони повинні бути очищені?

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

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

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

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

Також автор згадував про складнощі тестування одиниць. Але чи не буде у нас проблем із підходом до DI?

Ні. Оскільки ви не маєте залежності від статичного локатора обслуговування. Ви намагалися отримати паралельні тести, що працюють зі статичними залежностями? Це не весело.


2
Jgauffin, дякую за вашу відповідь. Ви вказали на одну важливу річ щодо автоматичної перевірки при запуску. Я не думав про це, і тепер бачу ще одну користь DI. Ви також навели приклад: "var myType = new MyType ()". Але я не можу вважати це дійсним, оскільки ми ніколи не встановлюємо залежності в реальному додатку (IoC Container це робить для нас постійно). Тобто: у додатку MVC у нас є контролер, залежність від IMyService та MyServiceImpl залежить від IMyRepository. Ми ніколи не інсталюємо MyRepository та MyService. Ми отримуємо екземпляри з парамерів Ctor (наприклад, від ServiceLocator) і використовуємо їх. Чи не ми?
davidoff

33
Ваш єдиний аргумент, що для "Локатора послуг" не є анти-шаблоном: "інверсія контейнерів управління не буде працювати без сервісного розташування". Цей аргумент, однак, недійсний, оскільки Локатор сервісів - це про наміри, а не про механіку, як тут чітко пояснив Марк Семан: "Контейнер DI, інкапсульований у кореневому складі, не є локатором обслуговування - це інфраструктурна складова".
Стівен

4
@jgauffin Web API не використовує службове розташування для DI для контролерів. Це зовсім не робить DI. Що це робить: це дає вам можливість створити власний ControllerActivator для переходу в служби конфігурації. Звідти ви можете створити корінь композиції, чи то Pure DI, чи Containers. Крім того, ви змішуєте використання місця розташування служби як складової вашого шаблону з визначенням "шаблону локатора послуг". З цим визначенням композиція Root DI може вважатися "шаблоном локатора обслуговування". Отже, вся суть цього визначення - суперечка.
Suamere

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

2
@jgauffin DI і SL - це версії IoC. SL - це просто неправильний спосіб зробити це. Контейнер IoC може бути SL, або він може використовувати DI. Це залежить від того, яким чином його проводять. Але SL - це погано, погано погано. Це спосіб приховати той факт, що ви щільно з'єднуєте все разом.
MirroredFate

37

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

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


12
Якщо ви маєте справу зі застарілим кодом, все виправдано, щоб позбутися вас від цього безладу, навіть якщо це означає робити проміжні (і недосконалі) кроки. Локатор послуг - це такий проміжний крок. Це дозволяє вам вийти з цього пекла на крок за той час, поки ви пам’ятаєте, що існує хороше альтернативне рішення, яке є документально підтвердженим, повторюваним та доведеним до ефективного . Це альтернативне рішення - введення залежності, і саме тому Локатор послуг все ще є антидіаграмою; правильно розроблене програмне забезпечення не використовує це.
Стівен

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

8

З точки зору тестування, Локатор обслуговування поганий. Дивіться приємне пояснення Google Tech Talk в Misko Hevery з прикладами коду http://youtu.be/RlfLCWKxHJ0 починаючи з хвилини 8:45. Мені сподобалась його аналогія: якщо вам потрібно 25 доларів, попросіть гроші, а не дайте свій гаманець, звідки будуть брати гроші. Він також порівнює Службовий локатор із стогом сіна, який має потрібну вам голку і знає, як її отримати. Класи, що використовують Service Locator, важко повторно використовувати через це.


10
Це думка, що переробляється, і було б краще, як коментар. Крім того, я вважаю, що ваша (його?) Аналогія служить доказом того, що деякі зразки є більш підходящими для деяких проблем, ніж інші.
8bitjunkie

6

Проблема з технічним обслуговуванням (що мене спантеличує)

Існує 2 різних причини, чому використання локатора служби погано в цьому плані.

  1. У вашому прикладі ви жорстко кодуєте статичну посилання на локатор служби у своєму класі. Це щільно з'єднує ваш клас безпосередньо з сервісним локатором, що, в свою чергу, означає, що він не буде функціонувати без сервісного локатора . Крім того, ваші тестові одиниці (і будь-хто інший, хто використовує клас) також явно залежать від локатора обслуговування. Одне, що, здається, залишається непоміченим, це те, що під час використання конструкторської ін'єкції вам не потрібен контейнер DI під час тестування одиниць , що значно спрощує ваші тестові одиниці (і здатність розробників їх розуміти). Це реальна вигода тестування одиниці, яку ви отримуєте від використання конструкторської інжекції.
  2. Що стосується того, чому конструктор Intellisense важливий, люди, схоже, повністю пропустили цю точку. Клас пишеться один раз, але він може використовуватися в декількох додатках (тобто декількох конфігураціях DI) . З часом вона виплачує дивіденди, якщо ви можете подивитися на визначення конструктора, щоб зрозуміти залежність класу, а не переглядати (сподіваємось оновлену) документацію або, якщо цього не зробити, повернутися до початкового вихідного коду (що може не бути бути зручним), щоб визначити, що таке клас класу. Клас із сервісною локатором, як правило, простіше написати , але ви більше, ніж платите за ці зручності за постійне обслуговування проекту.

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

Розглянемо випадок, коли вам потрібно скористатися послугою з того, LibraryAщо її автор вирішив би використовувати, ServiceLocatorAі послугу, LibraryBчий автор вирішив би використовувати ServiceLocatorB. У нас немає іншого вибору, крім використання двох різних локаторів обслуговування в нашому проекті. Скільки залежностей потрібно налаштувати - це здогадка, якщо ми не маємо гарної документації, вихідного коду чи автора на швидкому наборі. Якщо цих параметрів немає, нам може знадобитися використовувати декомпілятор простощоб з'ясувати, що це за залежності. Можливо, нам знадобиться налаштувати 2 абсолютно різних API локатора сервісу, і залежно від дизайну неможливо просто обернути наявний контейнер DI. Можливо, взагалі неможливо поділити один екземпляр залежності між двома бібліотеками. Складність проекту може бути ще більше ускладнена, якщо локатори сервісів фактично не перебувають у тих же бібліотеках, що і потрібні нам сервіси - ми неявно перетягуємо додаткові посилання на бібліотеку до нашого проекту.

Тепер розглянемо ті ж самі два сервіси, які виготовлені з впорскуванням конструктора. Додати посилання на LibraryA. Додати посилання на LibraryB. Надайте залежності у вашій конфігурації DI (проаналізувавши, що потрібно через Intellisense). Зроблено.

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


1

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

Ось абзац з адаптивного коду через C # :

"На жаль, локатор сервісів іноді є неминучим анти-шаблоном. У деяких типах додатків - особливо Windows Workflow Foundation - інфраструктура не піддається інжекціям конструктора. У цих випадках єдиною альтернативою є використання сервера-локатора. Це краще, ніж взагалі не вводити залежності. Для всіх моїх вітриолів проти (анти-) схеми це нескінченно краще, ніж побудувати залежності вручну. Зрештою, він все ще дозволяє ті найважливіші точки розширення, що надаються інтерфейсами, які дозволяють декоратори, адаптери, та подібні переваги ".

- Холл, Гері Маклін. Адаптивний код за допомогою C #: Agile кодування з моделями дизайну та принципами SOLID (Довідник розробника) (стор. 309). Пірсон освіта.


0

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

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

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

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


Зрін, дякую за думки. Як я розумію, при "правильному" підході до DI я не повинен створювати свої залежності ніде, крім одиничних тестів. Отже, компілятор допоможе мені лише з моїми тестами. Але як я описав у своєму первісному запитанні, ця "допомога" зі зламаними тестами мені нічого не дає. Робить це?
davidoff

Аргумент "статична інформація" / "перевірка часу збирання" - це солом'яна людина. Як зазначає @davidoff, DI однаково сприйнятливі до помилок виконання. Я також додам, що сучасні ІДЕ надають підказки для коментарів / підсумкової інформації для членів, і навіть у тих, хто цього не робить, хтось все ще буде шукати документацію, поки вони просто не знають API. Документація - це документація, будь-який необхідний параметр конструктора чи зауваження про те, як налаштувати залежність.
вівторок

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

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