Both those designs are fine, depending on the situation.
Both are a kind of dependency injection, there are 4 types of dependency injection:
- constructor : design A
- property : design B
- pass dependency when calling method
- ambient context
I will not elaborate on the 2 types you don't use here but they are discussed along with the other 2 in Dependency Injection in .NET of Mark Seeman
design A is constructor dependency injection and has the advantage that it is the most simple type of dependency injection. Your first choice of dependency injection is this one. If there is no good default for the dependency being injected, this is perfectly fine. The dependency does not change after the object is constructed.
Defaulting to this kind of dependency injection can lead to classes with many parameters in the constructor. This is referred to as dependency over injection.
I follow the same code as Mark Seeman does in his book so I have code like this :
public class TekeningViewModel : ViewModelBase
{
private readonly IUnitOfWorkAsync _unitOfWork;
TekeningViewModel(IUnitOfWorkAsync _unitOfWork){
if (unitOfWork == null)
{
throw new ArgumentNullException("unitOfWork");
}
_unitOfWork = unitOfWork;
}
}
note the readonly
type of member and the check that the parameter being passed is not null.
design B is ideal if there is a good default for the dependency being injected. Because then the property setter is perhaps not called, and a good default is set in the constructor automatically. design B is also a strategy pattern
. Where in design A it is not the intention to change to a different dependency on the fly, after construction that is, with design B you can do that. In a multi threaded environment this adds complexity to the way you have to implement it in a safe way. As explained design B leads to more code than design A.
Care must be taken when choosing a good default, because you don't want to create a dependency to an assembly just because of the default. Anyone using your class even those that don't use that default still would have a dependency towards the assembly that contains that default.
the third way to do dependency injection is like this :
Aclass{
void methodinjection(int a, Bclass b){
///
}
}
and the fourth way to do dependency injection is like this :
Aclass{
void methodinjection(int a){
AmbientContext::getInstance().getBclass();
//....
}
}
the complexity of the design keeps growing from 1 to 4.
Type 4 looks like a singleton which is an antipattern so it is my least favorite type of dependency injection but it does have its merit. Because the Ambient context itself should be created using dependency injection of the first 2 types and thus has a lot of flexibility. Ideal to pass cross cutting concerns to the application without polluting the constructor with too many parameters.