1

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 with DefaultA. If you intend to swap DefaultA with another default implementation you will have to modify B. – devnull May 8 '17 at 12:16
  • I don't think that's right. In any case where you use a Local Default you are introducing a coupling. It's not a tight coupling however, as you would only change that one line for a different default implementation, or if you wanted to remove it (of course you would have to modify B if you want a different Local Default). But all logic remains independent of DefaultA's implementation. – Danny Yaroslavski May 8 '17 at 12:29
  • This may be a duplicate of stackoverflow.com/q/21389283/126014 In any case, I'd prefer constructor overloads for languages that support that... – Mark Seemann May 8 '17 at 16:30
  • Thank you for replying! In this particular case, the language I'm working with does not support method overloads but does support default method arguments. Being able to provide a constructor signature that explicitly shows a default 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. – Danny Yaroslavski May 8 '17 at 17:05
0

Your idea works fine if there is only one optional dependency, but it starts to break down when there are multiple optional dependencies.

class B {

    var a:IA;
    var c:IC;

    public function new(a:IA = null, c:IC = null){
        if(a == null){
            a = new DefaultA();
        }
        this.a = a;

        if(c == null){
            c = new DefaultC();
        }
        this.c = c;
    }
}

With this class, it already becomes cumbersome to use a default A and a custom C. You can't just use

var b:B = new B(new ConcreteC());

because this would instantiate the B with a custom A (which happens to be a ConcreteC instance and most likely doesn't conform to the IA interface) and a default C. To use a custom C, you actually need to use

var b:B = new B(null, new ConcreteC());

so you have to explicitly state that your A is a null pointer. And if you have to specify your a argument anyway, then you might just as easily drop the support for default values and require the user to use

var b:B = new B(new DefaultA(), new ConcreteC());
  • From this answer it seems like for single Local Default's it's preferable to property injection, whereas for multiple Local Default's it's a tradeoff of cumbersomeness. To note; a Local Default would imply that it applies in most cases of instantiating B, such that we are using var b:B = new B() most of the time. We then have a tradeoff between having to (less often) use things like var b:B = new B(null, new ConcreteC()); versus always having to specify B's entire constructor; but this is more of argument for avoiding using multiple Local Default's than against this pattern. – Danny Yaroslavski May 8 '17 at 14:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.