Let's take a simple example-- perhaps you are injecting a means of logging.
Injecting a class
class Worker: IWorker
{
ILogger _logger;
Worker(ILogger logger)
{
_logger = logger;
}
void SomeMethod()
{
_logger.Debug("This is a debug log statement.");
}
}
I think that is pretty clear what is going on. What's more, if you are using an IoC container, you don't even need to inject anything explicitly, you just add to your composition root:
container.RegisterType<ILogger, ConcreteLogger>();
container.RegisterType<IWorker, Worker>();
....
var worker = container.Resolve<IWorker>();
When debugging Worker
, a developer just needs to consult the composition root to determine what concrete class is being used.
If a developer needs more complicated logic, he has the whole interface to work with:
void SomeMethod()
{
if (_logger.IsDebugEnabled) {
_logger.Debug("This is a debug log statement.");
}
}
Injecting a method
class Worker
{
Action<string> _methodThatLogs;
Worker(Action<string> methodThatLogs)
{
_methodThatLogs = methodThatLogs;
}
void SomeMethod()
{
_methodThatLogs("This is a logging statement");
}
}
First, notice that the constructor parameter has a longer name now, methodThatLogs
. This is necessary because you can't tell what an Action<string>
is supposed to do. With the interface, it was completely clear, but here we have to resort to relying on parameter naming. This seems inherently less reliable and harder to enforce during a build.
Now, how do we inject this method? Well, the IoC container won't do it for you. So you are left injecting it explicitly when you instantiate Worker
. This raises a couple problems:
- It is more work to instantiate a
Worker
- Developers attempting to debug
Worker
will find it is more difficult to figure out what concrete instance gets called. They can't just consult the composition root; they will have to trace through code.
How about if we need more complicated logic? Your technique only exposes one method. Now I suppose you could bake the complicated stuff into the lambda:
var worker = new Worker((s) => { if (log.IsDebugEnabled) log.Debug(s) } );
but when you are writing your unit tests, how do you test that lambda expression? It's anonymous, so your unit test framework can't instantiate it directly. Maybe you can figure out some clever way to do it, but it'll probably be a bigger PITA than using an interface.
Summary of the differences:
- Injecting only a method makes it harder to infer the purpose, whereas an interface clearly communicates the purpose.
- Injecting only a method exposes less functionality to the class receiving the injection. Even if you don't need it today, you may need it tomorrow.
- You cannot automatically inject only a method using an IoC container.
- You cannot tell from the composition root which concrete class is at work in a particular instance.
- It is a problem to unit test the lambda expression itself.
If you are OK with all of the above, then it's OK to inject just the method. Otherwise I'd suggest you stick with tradition and inject an interface.
Func
since you can name the parameters, where you can state their intent.