Constructor Injection has the advantage that it makes the dependency explicit and forces the client to provide an instance. It can also guarantee that the client cannot change the instance later. One (possible) downside is that you have to add a parameter to your constructor.
Setter Injection has the advantage that it doesn't require adding a parameter to the constructor. It also doesn't require the client to set the instance. This is useful for optional dependencies. This may also be useful if you want the class to create, for example, a real data repository by default, and then in a test you can use the setter to replace it with a testing instance.
Interface Injection, as far as I can tell, is not much different than setter injection. In both cases you are (optionally) setting a dependency that can be changed later.
Ultimately it is a matter of preference and whether or not a dependency is required. Personally, I use constructor injection almost exclusively. I like that it makes the dependencies of a class explicit by forcing the client to provide an instance in the constructor. I also like that the client cannot change the instance after the fact.
Often times, my only reason for passing in two separate implementations is for testing. In production, I may pass in a DataRepository
, but in testing, I would pass in a FakeDataRepository
. In this case I'll usually provide two constructors: one with no parameters, and another that accepts a IDataRepository
. Then, in the constructor with no parameters, I will chain a call to the second constructor and pass in a new DataRepository()
.
Here's an example in C#:
public class Foo
{
private readonly IDataRepository dataRepository;
public Foo() : this(new DataRepository())
{
}
public Foo(IDataRespository dataRepository)
{
this.dataRepository = dataRepository;
}
}
This is known as Poor Man's Dependency Injection. I like it because in production client code, I don't need to repeat myself by having several repeated statements that look like
var foo = new Foo(new DataRepository());
However, I can still pass in an alternate implementation for testing. I realize that with Poor Man's DI I'm hardcoding my dependency, but that's acceptable for me since I mostly use DI for testing.
If you donot like the additional constructor arguments for the dependencies
you need a DI-Container to handle the instance creation for you.
You can use either an existing di-container framework or implement a poor mans version on your own
public PoorMansDiContainer : IPoorMansDiContainer {
private IService mService = null;
private IFooService mFooService = null;
public IService getSingletonService() {
if (mService == null) {
mService = new ServiceImpl(getSingletonFooService());
}
return mService;
}
public IFooService getSingletonFooService() {
if (mFooService == null) {
mFooService = new FooServiceImpl();
}
return mFooService;
}
}
Best Answer
DI makes unit testing much easier. But you can still write unit tests without DI. Lots of unit tests have been written already before DI became widespread. (Of course, some of these used techniques identical or very similar to DI without knowing it has a fancy name :-)
I myself have used e.g. interfaces and factories a lot before learning about DI. The actual factory class name may have been read from a config file, or passed to the SUT as an argument.
Another approach is using singletons (or globally accessible data in general). Yes, I know it is not recommended by many (including myself) in general. Still it may be viable in specific situations, especially if the singleton contains static configuration data which is not test case specific, but differs between production and test environment. Of course it has its known problems, so DI is superior if you can use it. But often (e.g. in legacy systems) you can't.
Talking of which, Working Effectively With Legacy Code describes a lot of tricks to get legacy code covered by tests. Many of these are not nice, and aren't meant as a long term solution. But they allow you to create the first valuable unit tests to an otherwise untestable system... which enables you to start refactoring, and eventually (among others) introduce DI.