Я думаю, що найпростіший спосіб зрозуміти різницю між двома і чому контейнер DI настільки кращий, ніж локатор сервісу, - це думати про те, чому ми робимо інверсію залежності в першу чергу.
Ми робимо інверсію залежності, щоб кожен клас чітко зазначив, що саме залежить від роботи. Ми робимо це, тому що це створює найслабшу зв’язку, яку ми можемо досягти. Чим втрачається зв'язок, тим простіше щось тестувати та рефакторувати (і, як правило, потрібно менше рефакторингу в майбутньому, оскільки код чистіший).
Давайте розглянемо наступний клас:
public class MySpecialStringWriter
{
private readonly IOutputProvider outputProvider;
public MySpecialFormatter(IOutputProvider outputProvider)
{
this.outputProvider = outputProvider;
}
public void OutputString(string source)
{
this.outputProvider.Output("This is the string that was passed: " + source);
}
}
У цьому класі ми прямо заявляємо, що нам потрібен IOutputProvider і більше нічого для того, щоб цей клас працював. Це повністю перевірено і має залежність від одного інтерфейсу. Я можу перемістити цей клас у будь-яку точку своєї програми, включаючи інший проект, і все, що йому потрібно, - це доступ до інтерфейсу IOutputProvider. Якщо інші розробники хочуть додати до цього класу щось нове, що вимагає другої залежності, вони повинні мати чітке визначення того, що саме їм потрібно в конструкторі.
Погляньте на той самий клас із локатором обслуговування:
public class MySpecialStringWriter
{
private readonly ServiceLocator serviceLocator;
public MySpecialFormatter(ServiceLocator serviceLocator)
{
this.serviceLocator = serviceLocator;
}
public void OutputString(string source)
{
this.serviceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
Тепер я додав локатор служби як залежність. Ось проблеми, які відразу очевидні:
- Найперша проблема з цим полягає в тому, що для досягнення того ж результату потрібно більше коду . Більше коду - це погано. Це не набагато більше коду, але все-таки більше.
- Друга проблема полягає в тому, що моя залежність вже не є явною . Мені все-таки потрібно щось вписати в клас. За винятком того, що я хочу, не є явним. Це приховано у властивості речі, про яку я попросив. Тепер мені потрібен доступ і до ServiceLocator, і до IOutputProvider, якщо я хочу перенести клас на іншу збірку.
- Третя проблема полягає в тому, що додаткову залежність може взяти інший розробник, який навіть не усвідомлює, що приймає її, коли додає код до класу.
- Нарешті, цей код важче перевірити (навіть якщо ServiceLocator є інтерфейсом), оскільки ми маємо знущатися з ServiceLocator та IOutputProvider замість просто IOutputProvider
То чому б нам не зробити локатор сервісу статичним класом? Давайте подивимось:
public class MySpecialStringWriter
{
public void OutputString(string source)
{
ServiceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
Це набагато простіше, правда?
Неправильно.
Скажімо, що IOutputProvider реалізований дуже довгим веб-сервісом, який записує рядок у п’ятнадцять різних баз даних по всьому світу і потребує дуже тривалого часу для його завершення.
Спробуємо протестувати цей клас. Для тесту нам потрібна інша реалізація IOutputProvider. Як ми пишемо тест?
Ну а для цього нам потрібно зробити певну конфігурацію в статичному класі ServiceLocator, щоб використовувати іншу реалізацію IOutputProvider, коли вона викликається тестом. Навіть писати це речення було болісно. Запровадити це було б покрутливо, і це було б кошмаром технічного обслуговування . Нам ніколи не потрібно змінювати клас спеціально для тестування, особливо якщо цей клас не є класом, який ми насправді намагаємось перевірити.
Отже, вам залишається або а) тест, який викликає нав'язливі зміни коду у непов'язаному класі ServiceLocator; або б) взагалі немає тесту. І вам також залишається менш гнучке рішення.
Таким чином, клас локатор послуг повинен бути введений в конструктор. Що означає, що ми залишилися з певними проблемами, згаданими раніше. Локатор сервісів вимагає більше коду, повідомляє іншим розробникам, що йому потрібні речі, яких він не робить, спонукає інших розробників писати гірший код і дає нам меншу гнучкість рухатися вперед.
Попросту кажучи, локатори сервісів збільшують зв'язок у програмі та заохочують інших розробників писати високо зв'язаний код .