Why Dependency Injection?
Tags: swift, ios, tutorial
Published: 2022-02-16 22:40:26

In the  [previous post](https://dev.to/abdullahth/what-is-dependency-injection-68f) , we discussed the what of DI. In this post, we will explore the why. 

DI enables many benefits; before discussing those benefits, let us establish an example. let's say we have a view controller that can display news:

```swift
class NewsViewController: UIViewController {
    let newsLoader: NewsLoader
    
    init(newsLoader: NewsLoader) {
        self.newsLoader = newsLoader
        super.init(nibName: nil, bundle: nil)
    }
}
``` 

The `NewsViewController` uses DI because it doesn't create a `NewsLoader`. Instead, it states that it needs a `NewsLoader`. 
The `NewsLoader` is defined as a protocol:

```swift
protocol NewsLoader {
    func load(completion: (Result<[News], Error>) -> Void)
}
``` 

And we have two types that implement the `NewsLoader` protocol. One fetches the news from the network. The other fetches the news from a database:

```swift
class APINewsLoader: NewsLoader {
    func load(completion: (Result<[News], Error>) -> Void) {
        // load news from the network
    }
}

class DatabaseNewsLoader: NewsLoader {
    func load(completion: (Result<[News], Error>) -> Void) {
        // load news from a local database
    }
}
``` 

So you can use the same `NewsViewController` to display news from different sources. If you want to fetch news from the network, you use:

```swift
let apiNewsLoader = APINewsLoader()
let newsViewController = NewsViewController(newsLoader: apiNewsLoader)
``` 

If you want to display news from a database, you use:

```swift
let databaseNewsLoader = DatabaseNewsLoader()
let newsViewController = NewsViewController(newsLoader: databaseNewsLoader)
``` 

With this example established, let's explore the benefits of DI.

## Late binding
Late binding basically means that you bind the dependency later. That is, you decide which dependency to use at run time instead of compile time. 
For example, say we want to display news from the network only if the device is online. If the device is offline, we will display news from the database instead:

```swift
if Network.isOnline {
    let newsViewController = NewsViewController(newsLoader: apiNewsLoader)
} else {
    let newsViewController = NewsViewController(newsLoader: databaseNewsLoader)
}
``` 

## Dependency Inversion
If our `NewsViewController` doesn't use DI and creates its news loader dependency directly like so:

```swift
class NewsViewController: UIViewController {
    let newsLoader = APINewsLoader()
}
``` 

Then we can say our `NewsViewController` directly depends on `APINewsLoader`.

![Blank diagram - Direct Dependency.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1642306359231/h7RHZpug6.png)

However, when `NewsViewController` doesn't depend on `APINewsLoader` but depends on `NewsLoader`. Then in order to make `APINewsLoader` work with `NewsViewController` we need to confirm it to the `NewsLoader` protocol. So we inverted the dependency

![Blank diagram - Dependency Inversion.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1642306389544/Mk7z4AHkN.png)

Note that instead of the arrow going from `NewsViewController` to `APINewLoader`, the arrow goes back from `APINewsLoader` to `NewsLoader`, hence the name dependency inversion.

It is worth noting that `NewsLoader` should be owned by the module (or layer) that owns the `NewsViewController`. If not, then `NewsViewController` still has a direct dependency to another module/layer.

If we re-draw the previous diagram between the modules/layers instead of the individual classes, the dependency inversion concept will be more apparent.

So, without using `NewsLoader` to invert the dependency, we will have a direct dependency between the module that has `NewsViewController` and the module that has `APINewsLoader`

![Blank diagram - Copy of Direct Dependency-2.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1642306421753/ROeZs9nCE.png)

If we define `NewsLoader` inside the same module that has `APINewsLoader`, then we still have a direct dependency from the first module to the second one.

![Blank diagram - Incorrect.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1642306450431/inRiKouvq.png)

To invert the dependency, we define `NewsLoader` inside the same module that has `NewsViewController`.

![Blank diagram - Copy of Copy of Direct Dependency.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1642306484245/6VdTaIN2x.png)

Dependency inversion is an essential technique in architecture, where we might want to protect some modules from depending on others. Typically, we want our application code not to depend on frameworks. 

In our example, `APINewsLoader` indeed uses some framework to do the actual networking calls. Since `NewsViewController` doesn't directly depend on it, we can change or replace that framework without needing to change our UI.
 
## Testability 
To unit test the `NewsViewController`, we want to focus on the `NewsViewController` behavior itself, and not any collaborating objects. However, we cannot run `NewsViewController` without a `NewsLoader`. Since we are using DI to inject `NewsLoader`, we don't have to inject a real implementation such as `APINewsLoader` or `DatabaseNewsLoader`. Instead, we can define a **mock** implementation of `NewsLoader`

```swift
class MockNewsLoader: NewsLoader {
    func load(completion: (Result<[News], Error>) -> Void) {
        // No need to hit the real network or call the real database
        // you can return any news you want here to use in testing
    }
}

class NewsViewControllerTests: XCTestCase {
    func test_theThingYouWantToTest() {
        let mockNewsLoader = MockNewsLoader()
        let newsViewController = NewsViewController(newsLoader: mockNewsLoader)
        
        // test code here ...
    }
}
``` 

## Extensibility 
Let's say that you want to load news related to Apple only, and you want to reuse the same `NewsViewController` to display Apple news.
 
The two classes we defined above, `APINewsLoader` and `DatabaseNewsLoader`, return all news. We don't have to write, for example, `APIAppleNewsLoader` and `AppleDatabaseNewsLoader` and implement the same behavior except filtering the news. That is too much unnecessary work.

With proper DI, we can implement this feature without changing existing ones. We can define  `AppleNewsLoader` that implements the same `NewsLoader` and inject into it another `NewsLoader`. So we can reuse previous implementations and add on them (extent) new ones.

```swift
class AppleNewsLoader: NewsLoader {
    let newsLoader: NewsLoader
    
    init(newsLoader: NewsLoader) {
        self.newsLoader = newsLoader
    }
    
    func load(completion: (Result<[News], Error>) -> Void) {
        newsLoader.load { result in
            switch result {
            case .success(let news):
                let appleNews = getAppleNews(from: news)
                completion(.success(appleNews))
            case .failure(let error):
                completion(.failure(error))
            }
        }
    }
    
    private func getAppleNews(from: [News]) -> [News] {
        // filter and return Apple news ...
    }
}
```

To use our new `AppleNewsLoader` with news either from the network or the database, depending on the status of the device network, we can construct our objects as follow:

```swift
let newsLoader: NewsLoader = Network.isOnline ? APINewsLoader() : DatabaseNewsLoader()
let appleNewsLoader = AppleNewsLoader(newsLoader: newsLoader)
let newsViewController = NewsViewController(newsLoader: appleNewsLoader)
```

As you can see, DI enables us to compose and extent our objects in unprecedented ways! 

## Conclusion 
There are many benefits to DI. We only discussed the main ones. These primary benefits also, in turn, enable other benefits. For example, testability enables maintainability, and dependency inversion enables loose-coupling and parallel development.

I will continue to write about DI in my blog, and I'm planning to do a complete sample project. So if you are interested, please subscribe to my newsletter in my website [abdullahth.com](https://abdullahth.com).