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