To achieve a high degree of flexibility with DI, we can apply the following formula, driven by the SOLID principles:Object A should not know about object B that it is using. Instead, A should use an interface, I, implemented by B, and B should be resolved and injected at runtime.Let’s decompose this:
- Object A should depend on interface I instead of concrete type B.
- Instance B, injected into A, should be resolved by the IoC container at runtime.
- A should not be aware of the existence of B.
- A should not control the lifetime of B.
We can also inject objects directly without passing by an interface. It all depends on what we inject, in what context, and our requirements. We tackle many use cases throughout the book to help you understand DI.
Next, we translate this equation into an analogy that helps explain the reasons to use a container.
Understanding the use of the IoC container
To better understand the use of the IoC container and to create an image around the previous adaptability concept, let’s start with a LEGO® analogy where IoC is the equivalent of drawing a plan to build a LEGO®castle:
- We draw the plan
- We gather the blocks
- We press the start button on a hypothetical robot builder
- The robot assembles the blocks by following our plan
- The castle is complete
By following this logic, we can create a new 4×4 block with a unicorn painted on its side (concrete type), update the plan (composition root), and then press the restart button to rebuild the castle with that new block inserted into it, replacing the old one without affecting the structural integrity of the castle (program). As long as we respect the 4×4 block contract (interface), everything is updatable without impacting the rest of the castle, leading to great flexibility.Following that idea, if we need to manage every single LEGO® block one by one, it would quickly become incredibly complex! Therefore, managing all dependencies by hand in a project would be super tedious and error-prone, even in the smallest program. This situation is where an IoC container (the hypothetical robot builder) comes into play.
The role of an IoC container
An IoC container manages objects for us. We configure it, and then, when we ask for a service, the container resolves and injects it. On top of that, the container manages the lifetime of dependencies, leaving our classes to do only one thing, the job we designed them to do. No more need to think about their dependencies!The bottom line is that an IoC container is a DI framework that does the auto-wiring for us. We can conceptualize Dependency Injection as follows:
- The consumer of a dependency states its needs about one or more dependencies (contracts).
- The IoC container injects that dependency (implementation) upon creating the consumer, fulfilling its needs at runtime.
Next, we explore an code smell that applying Dependency Injection helps us avoid.
Leave a Reply