I've been reading through Mark Seemann's book on DI, and have a specific question with regards to implementing a Local Default.
The book advocates for property injection in this case, but I wanted to ask if the following is a simpler yet as versatile alternative: assigning a null default argument in the constructor parameter and changing that class's guard clause to assign the Local Default.
For example,
class B {
var a:IA;
public function new(a:IA = null){
if(a == null){
a = new DefaultA();
}
this.a = a;
}
}
This way, we leave the class open to being instantiated with a different IA
var b:B = new B(new ConcreteA());
As well as allow the class to be instantiated with no constructor arguments for brevity.
var b:B = new B(); // Uses DefaultA
We never need to worry about the a property being null, nor about creating property injection methods with guard clauses, because our constructor guarantees a will not be null.
Moreover, for users creating a new instance of B
, the = null
indicates both that it is optional AND that a different dependency can be passed in; in all other cases with DI we would never have that default parameter = null
scenario because our constructor arguments would be non-optional dependencies.
As a benefit, we can mix and match the constructor to allow for both default implementations and those that require a concrete class passed in.
class B {
var a:IA;
var c:IC;
public function new(a:IA, c:IC = null){
if(a == null){
throw NullException;
}
this.a = a;
if(c == null){
c = new DefaultC();
}
this.c = c;
}
}
As well as do things like
if(c == null){
c = new DefaultC(a); // If our DefaultC depends on an IA
}
this.c = c;
What are the downsides to this method? I realize that we've now put a small bit of logic into the constructor, but it really acts as a Default guard clause in a similar way we would call a setter with a Local Default parameter in the constructor if using property injection.
This method just seems too obvious to not have been recommended, so is there something I'm missing as to why this is bad for coupling/extendibility/misleading/etc?
B
is now tightly coupled withDefaultA
. If you intend to swapDefaultA
with another default implementation you will have to modifyB
.null
option just so happens to solve the discoverability problem you mentioned at the link. On the other hand the "Explicit is better than Implicit" problem remains, but I believe I can otherwise live with this design for simple cases.