Візьмемо простий приклад - можливо, ви вводите спосіб ведення журналу.
Ін’єкція класу
class Worker: IWorker
{
ILogger _logger;
Worker(ILogger logger)
{
_logger = logger;
}
void SomeMethod()
{
_logger.Debug("This is a debug log statement.");
}
}
Я думаю, що цілком зрозуміло, що відбувається. Більше того, якщо ви використовуєте контейнер IoC, вам навіть не потрібно нічого явно вводити, ви просто додаєте до кореня композиції:
container.RegisterType<ILogger, ConcreteLogger>();
container.RegisterType<IWorker, Worker>();
....
var worker = container.Resolve<IWorker>();
Під час налагодження Worker
розробнику потрібно просто проконсультуватися з корінком композиції, щоб визначити, який клас конкретного використовується.
Якщо розробнику потрібна більш складна логіка, у нього є весь інтерфейс для роботи:
void SomeMethod()
{
if (_logger.IsDebugEnabled) {
_logger.Debug("This is a debug log statement.");
}
}
Ін'єкційний метод
class Worker
{
Action<string> _methodThatLogs;
Worker(Action<string> methodThatLogs)
{
_methodThatLogs = methodThatLogs;
}
void SomeMethod()
{
_methodThatLogs("This is a logging statement");
}
}
По- перше, зверніть увагу , що параметр конструктора має більш довгу назву тепер methodThatLogs
. Це необхідно, тому що ви не можете сказати, що Action<string>
слід робити. З інтерфейсом це було абсолютно зрозуміло, але тут ми повинні вдатися до посилання на іменування параметрів. Це здається за своєю суттю менш надійним і важче застосувати під час збирання.
Тепер, як ми вводимо цей метод? Ну, контейнер IoC не зробить це за вас. Таким чином, вам залишається вводити це явно, коли ви створюєте інстанцію Worker
. Це викликає кілька проблем:
- Це більше роботи з інстанціюванням
Worker
- Розробникам, які намагаються налагодити помилку,
Worker
буде складніше зрозуміти, як називається конкретний екземпляр. Вони не можуть просто проконсультуватися із складом кореня; їм доведеться простежити код.
Як щодо того, якщо нам потрібна більш складна логіка? Ваша методика відкриває лише один метод. Тепер я припускаю, що ви можете спекти складні речі в лямбда:
var worker = new Worker((s) => { if (log.IsDebugEnabled) log.Debug(s) } );
але коли ви пишете одиничні тести, як ви перевіряєте це лямбда-вираз? Це анонімно, тому ваш тестовий модуль не може створити його безпосередньо. Можливо, ви зможете знайти якийсь розумний спосіб зробити це, але це, мабуть, буде більше PITA, ніж використання інтерфейсу.
Підсумок відмінностей:
- Введення лише методу ускладнює висновок про мету, тоді як інтерфейс чітко повідомляє про мету.
- Ін'єкція лише методу відкриває меншу функціональність класу, який отримує ін'єкцію. Навіть якщо вам це не потрібно сьогодні, він може знадобитися завтра.
- Ви не можете автоматично вводити лише метод, використовуючи контейнер IoC.
- Ви не можете сказати з кореня композиції, який конкретний клас працює в конкретному екземплярі.
- Проблема полягає в тому, щоб перевірити сам вираз лямбда.
Якщо ви все з вищезгаданим, вам все в порядку, тоді потрібно ввести лише метод. Інакше я б запропонував вам дотримуватися традицій та ввести інтерфейс.