Tomemos un ejemplo simple: tal vez esté inyectando un medio de registro.
Inyectando una clase
class Worker: IWorker
{
ILogger _logger;
Worker(ILogger logger)
{
_logger = logger;
}
void SomeMethod()
{
_logger.Debug("This is a debug log statement.");
}
}
Creo que está bastante claro lo que está sucediendo. Además, si está utilizando un contenedor de IoC, ni siquiera necesita inyectar nada explícitamente, solo agregue a la raíz de su composición:
container.RegisterType<ILogger, ConcreteLogger>();
container.RegisterType<IWorker, Worker>();
....
var worker = container.Resolve<IWorker>();
Al depurar Worker
, un desarrollador solo necesita consultar la raíz de la composición para determinar qué clase concreta se está utilizando.
Si un desarrollador necesita una lógica más complicada, tiene toda la interfaz para trabajar:
void SomeMethod()
{
if (_logger.IsDebugEnabled) {
_logger.Debug("This is a debug log statement.");
}
}
Inyectando un método
class Worker
{
Action<string> _methodThatLogs;
Worker(Action<string> methodThatLogs)
{
_methodThatLogs = methodThatLogs;
}
void SomeMethod()
{
_methodThatLogs("This is a logging statement");
}
}
Primero, observe que el parámetro constructor tiene un nombre más largo ahora methodThatLogs
,. Esto es necesario porque no se puede saber qué Action<string>
se supone que debe hacer. Con la interfaz, estaba completamente claro, pero aquí tenemos que recurrir a confiar en la denominación de parámetros. Esto parece inherentemente menos confiable y más difícil de aplicar durante una compilación.
Ahora, ¿cómo inyectamos este método? Bueno, el contenedor de IoC no lo hará por usted. Por lo tanto, queda inyectado explícitamente cuando crea una instancia Worker
. Esto plantea un par de problemas:
- Es más trabajo instanciar un
Worker
- Los desarrolladores que intenten depurar
Worker
encontrarán que es más difícil descubrir qué instancia concreta se llama. No pueden simplemente consultar la raíz de la composición; Tendrán que rastrear el código.
¿Qué tal si necesitamos una lógica más complicada? Su técnica solo expone un método. Ahora supongo que podrías hornear las cosas complicadas en la lambda:
var worker = new Worker((s) => { if (log.IsDebugEnabled) log.Debug(s) } );
pero cuando estás escribiendo tus pruebas unitarias, ¿cómo pruebas esa expresión lambda? Es anónimo, por lo que su marco de prueba de unidad no puede instanciarlo directamente. Tal vez puedas encontrar una forma inteligente de hacerlo, pero probablemente será una PITA más grande que usar una interfaz.
Resumen de las diferencias:
- Inyectar solo un método hace que sea más difícil inferir el propósito, mientras que una interfaz comunica claramente el propósito.
- Inyectar solo un método expone menos funcionalidad a la clase que recibe la inyección. Incluso si no lo necesita hoy, puede que lo necesite mañana.
- No puede inyectar automáticamente solo un método utilizando un contenedor IoC.
- No puede distinguir desde la raíz de la composición qué clase concreta está funcionando en una instancia particular.
- Es un problema probar unitariamente la expresión lambda misma.
Si está de acuerdo con todo lo anterior, entonces está bien inyectar solo el método. De lo contrario, te sugiero que sigas con la tradición e inyectes una interfaz.