Design patterns are reusable templates that help us solve software design problems using best practices. In this way, they help us build applications using code that is easier to maintain, understand, reuse and test.
What is this pattern for?
Dependency injection is a technique in which an object receives other objects that it depends on, called dependencies.
Let’s continue with the example of the application for a social network that we saw in the previous articles. When the user clicks on the icon of our app, the startup process begins, in which all the services and utilities that the app needs to function are loaded. These utilities can include databases, REST clients, interfaces to the phone’s sensors, and so on.
One way to create these services is in the application class itself, as shown in the following code.
class Application: def __init__(self): self.database = SQLiteDatabase() self.rest_api = RestApi()
However, doing this presents a number of problems. On the one hand, we are tying our application to the use of specific services. If in the future we want to use another type of database, we will have to modify the application code in all the places where the database has been used.
On the other hand, every time we want to test the application logic, we will have to create the services on which it depends, making it difficult to use unit tests and maybe even forcing us to perform connected tests that integrate all the services of the app.
In addition, since both classes are tied, it is difficult for different developers to work on them in parallel.
The solution: inject the services into the application.
How does it work?
Instead of creating the services inside the class that represents the application, we will pass them to the application as arguments when creating it:
class Application: def __init__(self, database, rest_api): self.database = database self.rest_api = rest_api ...
The part of the code where we pass the services to the app is known as the injector. In this part, we will have the opportunity to choose which services to pass to the app depending on the situation. For example:
if __name__ == '__main__': if execution_mode == 'test': database = MockDatabase() rest_api = MockRestApi() else: database = SQLiteDatabase() rest_api = RestApi()app = Application(database, rest_api) ...
In the code above, if the application is running tests, both the database and the REST API will be mock objects, which will emulate the functionality of the real services, without having to perform long-running operations. If, on the other hand, the application is running normally, we will pass the real services.
- Allows separate testing of the client and services by injecting mock services.
- Allows the services and the client to be developed separately.
- Allows the client to use different services in a flexible way.
- Separation of the use of services from their creation.