Давайте рассмотрим простой пример - возможно, вы вводите средства регистрации.
Впрыскивать класс
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.
- Вы не можете определить из корня композиции, какой конкретный класс работает в конкретном случае.
- Это проблема для модульного тестирования самого лямбда-выражения.
Если вы согласны со всем вышеперечисленным, тогда можно вводить только метод. В противном случае я бы посоветовал вам придерживаться традиции и внедрить интерфейс.