This document describes the basics of the dependency injection pattern; what it is; and why it is a good pattern to apply to your app development? The texts below uses the term DI as short for dependency injection.
Instead of describing the pattern in abstract terms, let's use a simple view controller based example to understand it. If you are interested in an abstract description, Wikipedia has a great article.
Let's say we are developing a photo browsing app, where we have a view controller that displays a set of photos retrieved from the server. In this extremely simple app, we have a PhotosViewController that displays the photos, and a PhotosService that encapsulates the logic of requesting photos from our server. The PhotosViewController implements the view logic while the PhotosService contains the HTTP request sending and response parsing logic. Without using DI, our PhotosViewController would instantiate a new instance of the PhotosService in its init or viewDidLoad method. Then it can use the service object to request photos when it sees fit.
Now let's step back and analyze our code. In its current state, the PhotosViewController and PhotosService are tightly coupled. This leaves us with a few issues:
- We cannot change
PhotosServicewithout having to also changePhotosViewController. This may seem fine with just two classes, but in a real-world scenario with hundreds of classes this would significantly slowdown our app iteration. - We cannot switch out the
PhotosServiceclass without having to changePhotosViewController. Let's imagine we have a betterPhotosServiceV2class we now want our view controller to use, we'll have to dig into the implementation ofPhotosViewControllerto make changes. - We cannot unit test
PhotosViewControllerwithout also invoking thePhotosServiceimplementation. - We cannot develop
PhotosViewControllerindependently and concurrently with thePhotosService. This may not seem like a big deal with our extremely simple app, in a real-world team, our engineers would be blocked constantly.
Let's apply the DI pattern to our app. With DI, we will have a third class, in Needle's terms a Component class, that instantiates the PhotosService and pass it into the PhotosViewController as a protocol. Let's call this protocol PhotosServicing. Now our PhotosViewController no longer knows anything about the concrete implementation of PhotosService. It simply uses the passed in PhotosServicing protocol to perform its logic.
With DI applied, let's revisit the issues we had before:
- We can freely change the implementation of
PhotosServicewithout affecting ourPhotosViewController. - We can simply update our DI
Componentclass to instantiate and passPhotosServiceV2intoPhotosViewController, as long as the implementation still conforms to thePhotosServicingprotocol. This allows us to freely switch implementations of the photos service without having to change anything in the view controller. - We can properly unit test
PhotosViewControllerby injecting, aka passing in, a mockPhotosServicingobject. - As soon as the
PhotosServicingprotocol is defined, we can independently and concurrently developPhotosServiceandPhotosViewControllerimplementations.
Before moving on, let's define some terminology that is frequently used with the DI pattern. In our example app above, the PhotosService is typically referred to as a "dependency". Our PhotosViewController class is sometimes referred to as the "dependent" or "consumer". The act of passing an instance of PhotosServicing into the PhotosViewController is called "inject". In summary, our simple DI setup injects the PhotosServicing dependency into the consumer PhotosViewController.