Dependency Injection Demystified — In Swift
As a new developer, when I started to learn how to use dependency injection, I found it oddly confusing and during my research, I discovered that many new developers also found the concept difficult to grasp. Yet, the concept isn’t difficult! I believe that once new developers understand why using dependency injection is a good practice, the concept will become more clear. In this article, I hope to demystify the dependency injection for beginners. Dependency injection is an incredibly powerful pattern and should be implemented in your codebase.
Let’s have a look at the definition of dependency injection:
In software engineering, dependency injection is a technique in which an object receives other objects that it depends on. These other objects are called dependencies. In the typical “using” relationship the receiving object is called a client and the passed object is called a service. — Wikipedia
That seems like a vague definition, but don’t worry, we are going to break this down. A simplified explanation of how a dependency injection works would be something along these lines; rather than a class instantiating the objects it encapsulates itself, it gets passed instances of these objects to use. These objects are then its dependencies and the act of passing them to the class is called injection.
Types of Dependency Injections
In this article we will be covering the most common types of dependency injections namely:
- Constructor Injection
- Property Injection
- Method Injection
- Bastard/Poor man’s Dependency Injection
The best way forward would be to start at the beginning, the first example we will be looking at is a class which has dependencies but does not make use of dependency injection:
No Dependency Injection:
Say we have a class called Band which has two dependencies called drummer and guitarist. To have a functional Band class without using dependency injection we would have to do something like this:
Constructor Injection:
Now we can improve the above code and implement constructor injection, here we will pass the dependencies through the constructor of Band.
Benefits when using constructor injection include the ability to have the dependencies remain immutable, as we are able to declare the properties as constants.
Property Injection:
Here we are going to be implementing a property injection, we declare public optional variables and set them after we instantiate the band object, effectively injecting the Band class with the dependencies it requires.
Property injection is also a great way to use dependency injection however, in this case, we need to declare the properties as mutable which is not ideal as it can be modified accidentally. Another shortcoming compared to constructor injection would be that if you are working in a large codebase or a codebase which you are not familiar with, when creating an object of Band you will not necessarily know that it has dependencies without going to investigate the Band class and this could possibly cause a runtime error.
Method Injection:
Method injections are quite flexible and offer the developer a lot of freedom, as you are able to instantiate an object and even make use of the functions that do not require dependencies, and only inject the dependencies where they are needed.
One of the limiting factors that Method Injection introduces is that the Band class does not have full access to the dependencies as a whole.
Bastard/Poor Man’s DI:
Now, this may seem similar to the constructor injection, however, we are providing default parameters, which are, essentially, default dependencies. This will improve the ability to test however this is still considered bad practice as if you were to use the default constructor it is tightly coupled to the implementation of the dependencies.
So, something worth noting is that you should avoid implementing Bastard Injection.
Why you should be using Dependency Injection:
There are multiple benefits when using dependency injection, here are the main reasons you should be making use of dependency injection:
Readability:
Once you have adopted the dependency injection pattern the structure of the classes and project as a whole becomes a lot more clear and easier to read. Once you get comfortable with the pattern you will start to know where to look for certain things and what certain classes will require.
Testing:
Unit testing is quite difficult to do without using dependency injection, once you follow the pattern it becomes significantly easier to unit test as you are able to mock out dependencies easily.
Separation of concerns:
Using dependency injection also prevents your code from being tightly coupled together as well as promotes the separation of concerns design principle.
In conclusion:
There are many other methods you can use to implement the dependency injection pattern into your projects. These are simply the most common ways. Lastly, I would like to encourage new developers to embrace topics that they do not understand and discover why they are used.