Many articles talk about DIP, but few explain how to invert a dependency.
What Inversion Means in the Dependency Inversion Principle
By the end of the article, you will know how to invert dependencies and why it is not just about creating abstractions.
But before understanding how to invert a dependency, we need to understand what a dependency is.
What Is a Dependency?
A dependency is any component required to work. From the DIP perspective, a dependency is any code required to deploy another.
Examples
- When you have a method that asks for arguments, your method depends on the data of those arguments. Furthermore, the types of those arguments are dependencies.
- If you have a class whose properties require different types, those types are dependencies of your class.
// Book.swift
class Book {
let name: String
}
// Library.swift
class Library: {
var books: [Book]
}
In the above example, the type ‘Book’ is a dependency of Library because we can’t compile the library.swift if book.swift doesn’t exist.
What Is Dependency Inversion
The Dependency Inversion Principle (DIP) tells us that the most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions.
– Robert C. Martin Clean Architecture.
Abstractions can be Interfaces in Java, Protocols in Swift, or Abstract Classes in other languages.
But just changing classes for abstractions doesn’t mean we are inverting a dependency.
How To Invert a Dependency
Let’s check again our example with the library.
// Book.swift
class Book {
let name: String
}
// Library.swift
class Library: {
var books: [Book] = []
}
I can’t compile the Library.swift file without compiling before the Book.swift means the Library depends on the Book file. This doesn’t seem correct because a library could store other kinds of items, like magazines.
So, let’s refactor our code.
// Book.swift
protocol Item {
let name: String
}
class Book: Item {
let name: String
}
// Library.swift
class Library: {
var items: [Item] = []
}
I changed my code to use an abstraction instead of a concrete type. Now I can inject into my Library any Item and not only Books. This is more flexible, but did I invert the dependencies?
No, the dependencies were not inverted.
Since the Item abstraction still lives in the Book.swift file. The Library.swift file still depends on that file. Without the book file, I can’t compile, deploy, or ship a Library. The Library still depended on Books; the Dependency Inversion Principle was not fulfilled.
Let’s move the Item abstraction to the Library file and see what happens.
// Book.swift
class Book: Item {
let name: String
}
// Library.swift
protocol Item {
let name: String
}
class Library: {
var items: [Item] = []
}
I have the same abstraction and classes; I just changed where the abstraction lives. But such a simple change makes the Library file independent of the Book file. I can now deploy Libraries even if the Book file doesn’t exist.
The Library no longer depends on the Book; the Book is the one that depends on the Library. The dependency was inverted.
But How Do I Know What Should Depend on What?
So, we refactored the code to invert the dependencies, but how do I know this is correct? What if I want to make books independent of libraries?
First, imagine you are designing the Library class, and it’s required to store a wide range of items, as long they have a name. If they fulfill this requirement, they can be stored in the Library. This means you will decide based on the Library’s needs rather than the Book’s.
But now you have a new dilemma. You want to deploy books without libraries. Because you also want to sell them in a bookstore. This problem can be solved easily by implementing the abstraction in a different file (or module).
// Library.swift
protocol Item {
let name: String
}
class Library: {
var items: [Item] = []
}
// Book.swift
class Book {
let name: String
}
// Book+Library.swift
extension Book: Library {}
🎉 With this change, Libraries and Books are independent of each other. What is important is that the abstraction (Item) is declared on the consumer (Library) side.
What inversion means in the Dependency Inversion Principle was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.