Dependency Injection (DI from now on) is a programming technique that makes a class free of its dependencies. The way it achieves this is by decoupling the usage of an object from its creation i.e passing dependencies as a parameter instead of creating them or hardcoding them on the same class, helping to contribute to the single responsibility principle 🙌.
What does Dependency Injection help with?
DI allows us to develop loosely coupled code, which means making the components that conform the code less dependent on each other 🤓. It is a great strategy to reduce tight coupling between software components enabling us to better manage future changes and other complexity in software. We could say that the purpose of DI is to make code maintainable by decreasing coupling between classes and their dependencies.
By removing a class’s knowledge of how its dependencies are implemented, software becomes more reusable, testable and maintainable.
Advantages of including DI into a project
DI helps us to loose coupling and to follow SOLID and single responsibility principles.
As for SOLID design principles, their goal is to improve the reusability of your code and reduce the frequency with which you need to change a class. Dependency injection supports these goals by decoupling (separating/segregating) the creation from the usage of an object.
That enables us to replace dependencies without changing the class that uses them. It also reduces the risk that one has to change a class just because one of its dependencies changed.
Dependency Injection and unit testing
As a collateral effect DI contributes to the software testability.
Unit tests tend to go hand-in-hand with improving the code’s separation of concerns; the more well-abstracted and organized the code is, the easier it will be to unit test it.
This separation of concerns is directly linked with the single responsibility principle we mentioned, before because when applying dependency injection we are able to decouple objects. This gives control over the object/class’s behavior while testing them.
By allowing the dependencies to be injected, we can unit test the logic of whatever class by providing a "mock" or stub object (the dependencies).
So in this way DI allows us to inject mocks or stubs into the system under test in order to define repeatable, deterministic unit tests.
Some types of Dependency Injection
Dependency Injection is the term used when you write more flexible code that allows the dependent classes to be changed without having to change the class code of the dependencies.
There are different ways to inject objects into another one, next we will address some of them:
This is a specific type of DI technique which means that we pass the required components (dependencies) into a class at the time of instantiation. It’s the most straightforward and natural one.
The constructor forces us to provide valid objects for all dependencies and also enforces immutability.
One problem with the constructor injection is that if your class has many dependencies, then your constructor signature gets unwieldy and hard to read. Also, the constructed object is immutable, no possibility to change object’s dependencies (inflexibility) later although it can be combined with setter injection. .
Using constructors to create object instances is natural from the OOP standpoint however one disadvantage of constructor injection might be its verbosity when a class/object has a handful of dependencies.
Another way is to use setters; meaning that when you have multiple dependencies that you need to inject, each one is a separate set method so in this way we don’t have a long list of constructor parameters. Also it offers mutability since it gives us more flexibility in dependency resolution or object reconfiguration because it can be done anytime.
One disadvantage is that it could be potentially more error-prone and less secure than constructor injection due to the possibility of overriding dependencies.
Dependency Injection Container
As mentioned above, when you have multiple dependencies in a class, the amount of code that you have to write to use this class gets bigger (because of the setters). As a result, if you use the class a lot you will end up writing a lot of extra boilerplate code every single time (just to configure the class).
The solution to this problem is called 👉 Dependency Injection Container.
A Dependency Injection Container, as its name states, is an object that holds all the dependencies so that whenever a class needs a dependency it can access it from the container. In order for this to happen you should have previously instantiated all the dependencies on the container so the classes that need these dependencies can have access to them when needed.
With this, a Dependency Injection Container is an object that is responsible for recording, deciding, and settling all the dependencies. Responsibilities of the deciding and settling mean that the DI container needs to know about the constructor arguments and the relationships between the objects.
When deciding which approach to choose, dependency injection container might be the right option when handling numerous different objects with many dependencies.
So now you know it! Whenever you have to start a new project, be aware of making some time to include DI.
- It favors the development of loosely coupled code contributing to the separation of concerns, SOLID and single responsibility principles.
- By removing the creation of the dependencies of a class, software becomes more reusable, testable and maintainable.
- Mocking data for tests should be easy, since it allows the dependencies to be injected.
- DI is independent of the software architecture used.
- Dependency Injection is agnostic to any framework or architecture (whether the architecture used is MVC or MVVM or whatever variation to these you are using) meaning that it can be applied to any context.
- Constructor Injection, Setter Injection and DI Container are some of the ways you can inject data into the classes.
Stay tuned for part two of this post, in which we are going to show a couple of examples for iOS using Swift features!