Vamos dar um exemplo simples - talvez você esteja injetando um meio de registro.
Injetando uma classe
class Worker: IWorker
{
ILogger _logger;
Worker(ILogger logger)
{
_logger = logger;
}
void SomeMethod()
{
_logger.Debug("This is a debug log statement.");
}
}
Eu acho que está bem claro o que está acontecendo. Além disso, se você estiver usando um contêiner de IoC, nem precisará injetar nada explicitamente, basta adicionar à raiz da composição:
container.RegisterType<ILogger, ConcreteLogger>();
container.RegisterType<IWorker, Worker>();
....
var worker = container.Resolve<IWorker>();
Ao depurar Worker
, um desenvolvedor precisa apenas consultar a raiz da composição para determinar qual classe concreta está sendo usada.
Se um desenvolvedor precisar de uma lógica mais complicada, ele terá toda a interface para trabalhar:
void SomeMethod()
{
if (_logger.IsDebugEnabled) {
_logger.Debug("This is a debug log statement.");
}
}
Injetando um método
class Worker
{
Action<string> _methodThatLogs;
Worker(Action<string> methodThatLogs)
{
_methodThatLogs = methodThatLogs;
}
void SomeMethod()
{
_methodThatLogs("This is a logging statement");
}
}
Primeiro, observe que o parâmetro construtor tem um nome mais longo agora methodThatLogs
,. Isso é necessário porque você não pode dizer o que Action<string>
é suposto fazer. Com a interface, ficou completamente claro, mas aqui precisamos recorrer à nomeação de parâmetros. Isso parece inerentemente menos confiável e mais difícil de aplicar durante uma compilação.
Agora, como injetamos esse método? Bem, o contêiner de IoC não fará isso por você. Então você fica injetando explicitamente quando você instancia Worker
. Isso levanta alguns problemas:
- É mais trabalho instanciar um
Worker
- Os desenvolvedores que tentarem depurar
Worker
descobrirão que é mais difícil descobrir como uma instância concreta é chamada. Eles não podem simplesmente consultar a raiz da composição; eles terão que rastrear o código.
Que tal se precisarmos de lógica mais complicada? Sua técnica expõe apenas um método. Agora, suponho que você possa assar coisas complicadas no lambda:
var worker = new Worker((s) => { if (log.IsDebugEnabled) log.Debug(s) } );
mas quando você está escrevendo seus testes de unidade, como você testa essa expressão lambda? É anônimo, portanto sua estrutura de teste de unidade não pode instanciar diretamente. Talvez você possa descobrir uma maneira inteligente de fazer isso, mas provavelmente será uma PITA maior do que usar uma interface.
Resumo das diferenças:
- Injetar apenas um método dificulta inferir o objetivo, enquanto uma interface comunica claramente o objetivo.
- Injetar apenas um método expõe menos funcionalidade à classe que recebe a injeção. Mesmo se você não precisar dele hoje, poderá precisar dele amanhã.
- Você não pode injetar automaticamente apenas um método usando um contêiner de IoC.
- Você não pode distinguir da raiz da composição qual classe concreta está funcionando em uma instância específica.
- É um problema fazer o teste de unidade da própria expressão lambda.
Se você estiver bem com todas as opções acima, não há problema em injetar apenas o método. Caso contrário, sugiro que você siga a tradição e injete uma interface.