Dependency Inversion is the all-encompassing principle. Its states that dependencies need to be inverted. Dependency Injection is the specific act of inverting dependencies. Constructor Injection is when you are performing dependency injection through constructor of an object. Parameter Injection is when you perform dependency injection through parameter of any function of an object.
Dependency Injection (DI) is about never coupling to concretion but always coupling to an abstraction. The point of performing DI is it allows to change instances of classes at run time, not only on compile time. It Increases flexibility or reuse of the code. Increases small classes with limited responsibility and makes it easy to exchange one instance for another instance in order to change particular behavior.
Without DI, unit testing and isolation would be tricky because when you want to test in isolation you want to test a class without interaction with other classes. With DI We can use mock so that we can test the code in isolation.
Let’s take an example and implement dependency injection on it. Below example shows an IAnimal interface having Speak method and we have concrete classes Cat, Cow and Dog implementing the interface and having own implementation of Speak method. Ideally the speak method can be in separate class by itself for these concrete classes instead of hardcoding them inside the concrete implementation.
Below example shows an interface ISpeakingBehavior having a method Speak definition. This method is implemented by concrete classes CowSpeakingBehavior, DogSpeakingBehavior, CatSpeakingBehavior where different implementations of Speak method are provided. These classes are now instantiated in the Dog, Cow & Cat classes and their respective speak method is invoked. Here comes the problem of instantiating dependencies inside the class.
If the Cat instantiates CatSpeakingBehavior and Dog instantiates DogSpeakingBehavior we are potentially breaking the DI principle because high level module (Cat, Dog & Cow) are coupled to their low level modules (CatSpeakingBehavior, DogSpeakingBehvaior & CowSpeakingBehavior). These dependencies need to be inverted so that both high level and low level modules depend on abstractions. The abstractions in this case are IAnimal and ISpeakingBehavior. DI in this case states that Animals should not be coupled to concrete types of speaking behavior but rather to the abstractions.
So, the Animal classes should receive the speaking behavior and then run the method speak on that speaking behavior. How they would receive that will be either by constructor or parameter injection. The point here is the concrete animal class (Cat, Dog & Cow) then only knows about abstraction of speaking behavior and doesn’t know about the concrete speaking behavior like CatSpeakingBehavior, CowSpeakingBehavior etc.
In many cases DI is enough, and you might not need inheritance hierarchies. That’s why people often prefer composition over inheritance because you can compose what behaves as a sub type instead of constructing it. In this example assuming animal classes do nothing else all of them contain the same code which is redundant. In that case we only need Animal class which allows us to achieve composition over inheritance because when we want to instantiate Cat, we instantiate Animal class and provide it with CatSpeakBehavior and thus the animal instance will behave as Cat.
If you take DI to extremism, then you would realize you can always inject dependencies instead of instantiating dependencies. This is also the reason why the new keyword is dangerous as you need to think if this is the right place to couple to concretion instead of abstraction. As mentioned earlier DI is also very helpful in mocking and unit testing.
GitHub Link: DependencyInjection#6DayOf100DaysOfCode
Comments