I have class A that depends on interfaces B & C. I use constructor injection to inject them into A:
class A {
let b: B
let c: C
init(b: B, c: C) {
self.b = b
self.c = c
}
}
This is mostly so I can inject mock objects for testing.
However, 80% of my use cases use the default implementations of B & C (let's call them, BImpl and CImpl). There's also some edge cases that make up 20% of my use cases.
Is it a good idea to create a "default" initializer where the constructor is parameterless and sensible defaults are created?
init() {
self.init(b: BImpl(), c: CImpl())
}
The initializer delegates to the previous (parameterized) initializer, so I don't lose my ability to inject my own (custom) dependencies if I wanted to. But this way, I can also choose to roll with the default values if I want to.
Does this have some unforeseen disadvantages?
2 Answers 2
One of the benefits of dependency injections is that you can compile, test, and use A
without "knowledge" of BImpl
or CImpl
, but by adding them as defaults, you lose that benefit. You can still use dependency injection in your edge-cases (though Philip makes a good case about transitive dependencies), but you will always be paying the price for having the defaults, used or not.
If your project is small enough that it's all in one module and build times don't bother you, it's probably not a "real" problem. If you are working on something big or writing library code, you should pay careful attention to which modules know about each other, and which direction that knowledge goes.
You might want to use a "dependency injection container" to manage these default dependencies, rather than coding it into all of your classes. It can even be more convenient.
-
So what you're saying is that if we decide to say, remove BImpl and CImpl, we would have to modify A as well, since our default constructor is coupled to it (v.s. DI-only). (However, this isn't major enough to create problems on a massive scale.) Do you have any proposed solution that doesn't use an IOC container?RodYt– RodYt2020年07月06日 09:41:16 +00:00Commented Jul 6, 2020 at 9:41
-
@RodYt you can use a factory or builder to set up
A
as Pedro suggests, or simply provide the instances of B and C directly in the code that creates an A, like I assume you are doing in the special cases..Jacob is on Codidact– Jacob is on Codidact2020年07月06日 12:37:55 +00:00Commented Jul 6, 2020 at 12:37
Should A, B, C, know you have an application D where B and C are always BImpl and CImpl? I don't think so. That is a concern of D and no one else.
What need is a factory method.
Have a method that encapsulates that default logic for the D application, and leave that out of the actual interfaces and implementations.
Explore related questions
See similar questions with these tags.
B
andC
, so have to test that functionality as well.