Dependency Injection
Inversion of Control Is a pattern of providing any type of object (that implements or controls something) instead of directly doing so. It is an act of inverting and or redirecting the control to an external agent.
Dependency Injection Is a specific usage of inversion control. Implementations are somehow passed on to the object, then this object starts to depend on it to be able to execute its behavior correctly.
Example Instead of calling a specific library directly, an app calls a contract or interface that refers to the concrete implementation. Which is controlled by the application instead of the library.
Pros
- Low coupling
- Testability
- Reusability
- Legibility
- Separation of Concerns
Cons
- Complexity
- Highly coupled to the DI framework
Basic Dependency Injection
Initializer Based
The dependencies are passed over to the object by its initializer
Instead of relying on the singleton, we now utilize the NetworkProtocol
, this is already a form of dependency injection. We can then test like the following:
Property Based
The dependency is injected from a property that can be modified externally. This is a good option when you have no control over the object’s initialization such as when you have ViewControllers from storyboards. Example of testing
Parameter Based
The dependency is injected from a parameter of a function. It’s a good option for testing legacy methods without breaking changes. Instead of passing the dependency to the initializer like an initializer-based DI, we pass it as a parameter of a function.
Advanced Dependency Injection
Singletons
Singletons are not always bad, it really depends on HOW, WHEN, and WHY are you using them. Just don’t make them ‘God’ classes with huge responsibilities. Make them as single-purpose as you possible by just taking care of a single aspect of the app.
- Extract its protocol & interface
- Conform to it
- Apply one of the basic techniques from before It’s not gonna solve all of your problems, but it is enough to some extent for a simple application. However, it will become a problem when there is modularization involved & can introduce unwanted coupling. Further reading: https://www.pointfree.co/blog/posts/21-how-to-control-the-world
Another way you might create a DI for a singleton is through the Singleton+ (read: singleton extended) pattern. You do not need to create private initializers like before. It’s gonna allow us to have more control over the shared state while still keeping the shared instance as an option. This implementation uses open access modifier to make it possible to be inherited by other classes so we can control some functions or properties. Now, how will we test this?
Factories
This design pattern focuses on solving the issue of creating objects without making their concrete types explicit. The objective here is to encapsulate (hide) implementation details about how the object is created. The advantage is that the consumer does not need to know the internal logic of how the object is created since we provide a common interface.
For example, we have the following view controller, and when we want to create tests, it would be really hard since the VC requires some dependencies. So what we can do is create a factory, essentially we are saying “hey factory please give me this VC, I don’t care how you do it but I just want this VC” Now, we can use our factory inside the view controller.
Service Locator
It is basically a big singleton of instances. It is a pattern that provides access to services/dependency instances. Resolver will tell you how to resolve something & Container will hold the dependency. When we want to register something, we simply get the key & save it to the dictionary. The first function is for the regular instances and the second function is for the lazy instances. When you register a lazy instance, you register effect reference(?), it’s a closure & connect a specific key to the factory. https://www.youtube.com/watch?v=M0c6DGNOUYc