What is Dependency Injection?
This is a technique where one object supplies the dependencies of another object .
Dependency injection means giving an object its instance variables.
Loosely coupled code is the main goal of Dependency Injection. It enables us to write loosely coupled code
Why Dependency injection ????
For a good code design, it’s very important to use proper abstractions because they make our code loosely coupled. That means that different components of our code can be replaced with alternative implementations without affecting other components.
When our code is loosely coupled it becomes easier to test, easier to extend, easier to reuse, easier to develop in parallel. That all makes it easier to maintain.
Loosely coupled code is the main goal of Dependency Injection.
It enables us to write loosely coupled code. And thus it makes testing, extending and reusing code much easier.
Understanding Dependency injection Approach with below example
class ViewController: UIViewController {var serviceManager: ServiceManager?}
In above example, we define a UIViewController subclass that declares a property, serviceManager, of type ServiceManager?.
Here we can set the value of serviceManager by one of the Two ways -
- Without Dependency injection
- With Dependency Injection
Setting the value of serviceManager without Dependency Injection -
class ViewController: UIViewController {var serviceManager: ServiceManager? = ServiceManager()}
By this way, the view controller is in charge of creating the ServiceManager instance.
This means that the ViewController class not only knows about the behavior of the ServiceManager class. It also knows about its instantiation.
Setting the value of serviceManager with Dependency Injection-
// Initialize View Controllerlet viewController = ViewController()// Configure View ControllerviewController.serviceManager = ServiceManager()
The second option, with dependency injection, is to inject the ServiceManager instance into the ViewController instance.
By injecting the request manager, the view controller doesn’t know how to instantiate the request manager.
Many of us immediately discard this option because it’s unnecessarily complex. But if you consider the benefits, dependency injection becomes more appealing.
Types of Dependency injection
There are three-way for dependency injection
- Initializer Injection also called Constructor Injection
- Property injection
- Method injection
Initializer Injection
This type of injection supports dependency injection through an initializer. This is also called Constructor Injection.
class ServiceManager {private let dataManager: DataManagerinit(dataManager: DataManager) {self.dataManager = dataManager}}// Initialize data managerlet dataManager = DataManager()// Initialize ServiceManagerlet serviceManager = ServiceManager(dataManager: dataManager)
Here the only way to set the dataManager property is bypassing it as an argument during initialization.
This way gives you benefit like-
The init(dataManager:) method is the designated initializer and guarantees that the ServiceManager instance is correctly configured
The most important benefit is that dependencies passed in during initialization can be made immutable. That means in this example the ‘dataManager ‘property cannot be mutated.
Because we’re required to pass the ‘dataManager’ as an argument during initialization, the designated initializer clearly shows what the dependencies of the ServiceManager class are.
Property injection
This type of injection supports dependency injection using properties.
Dependencies can also be injected by declaring an internal or public property on the class or structure that requires the dependency.
class ViewController: UIViewController {var serviceManager: ServiceManager?}// Initialize View Controllerlet viewController = ViewController()// Configure View ControllerviewController.serviceManager = ServiceManager()
Method Injection (Parameter-based Injection)
This is also called Parameter-based Injection.
Dependencies can also be injected whenever they’re needed. This is easy to do by declaring a method that accepts dependency as an argument.
This type of injection is particularly useful when you want to easily make legacy code more testable, without having to change too much of its existing structure.
In the below example, the dataManager isn’t a property on the ServiceManager class. Instead, the dataManager is injected as an argument of the intiateRequest(_:withrequest:) method.
class ServiceManager {func intiateRequest(dataManager: DataManager, withrequest request: Request) -> Data? {.......
}
}
Benefits of Dependency Injection:-
- Loose Coupling: Dependency injection can reduce coupling in a project.
- Separation of concern: This makes our code separated with limited functionality like as we have seen in the above example Service Manager is responsible for the behavior of its request, it isn’t and shouldn’t be concerned with its instantiation.
- Improve Testability: Unit testing is much easier with dependency injection. Dependency injection allows developers to replace an object’s dependencies with mock objects, which makes isolating behavior and setting up unit tests easier and less complicated.