Dependency inversion is a way to decouple software modules by inverting connections between them and using intermediate abstractions to hide implementation details. This helps to create independent modules, change or replace one module without touching dependent modules. Here are just some benefits:
- Modules can be reused in multiple apps;
- Code can be tested independently by mocking dependencies;
- Frameworks and libraries can be updated and replaced easily.
Dependency inversion is a part of SOLID principles used as a best practice guide in modern software design.
What makes it inversion?
Dependency means that you have module A that uses, therefore depends on, module B:
This creates top to bottom connection, the high-level module depends on the low level one. Apple uses it in many OS frameworks, like SwiftUI and UIKit both depend on Core Animation.
Top to bottom connections are logical — if you want to build a house you depend on a foundation.
But what if we want to change the low-level module? Changes in module B require changes in dependant module A.
The dependency inversion principle introduces an abstraction in between two modules. Let’s name it A*. Module A defines and uses interface A*, module B implements it:
Now both modules depend on abstraction and the connection is inverted. A* hides implementation details. If we want to change or replace module B with module C we need to simply implement interface A*:
Consider this as the high-level module presenting its requirements, and the low level one implementing them.
Dependency inversion implies that module B and C both know interface A*. This is fairly simple to achieve for modules in the same project using extensions in Swift.
Independent modules can be connected using the adapter pattern:
Typically the adapter is implemented on an app or services layer.
Implementing Dependency Inversion
First we need to start with separation of abstraction layers. Refactoring code to aggregate all candidate methods under an abstract interface and implementing it in a concrete class. This class can be taken out to a separate module.
For new features dependency inversion can make coding faster because you can easily create mocks for dependencies, that may not exist yet.
To demonstrate dependency inversion principle I decided to create the app to displays a list of posts. I use JSONPlaceholder service for test data.
I created two networking modules: NetworkingA uses
URLSession, NetworkingB uses Alamofire. Maybe I want to see which one works better ☺️
Different frameworks declare different APIs so mine are also a bit different.
With top to bottom structure my high level modules would depend on low level networking module to get data. But having two networking modules would be problematic.
I have higher level Services module that contains
UserService class. It declares
UserNetworkService protocol — abstraction for dependency inversion.
Services module includes NetworkingA and NetworkingB so I can use Swift extensions to implement conformance to
My structure looks like this:
Now I can easily replace one networking module with another.
This example implies that Services module includes NetworkingA and NetworkingB. What if our modules are independent? PostsUI is an example of such module. It displays a list of posts and declares its dependency in
The demo app implements
DataProvider using the adapter for networking and services modules.
PostsUI is independent of other modules:
Structure for PostsUI looks like this:
PostsUI also uses SwiftUI and it is convenient to use previews while developing. For this PostsUI implements a mock for
DataProvider with empty data. Same, but with test data, can be used if we want to add unit tests.
Having these different implementations, in different modules, is easy with dependency inversion principle.
Dependency inversion introduces an abstraction in between two modules to remove strong coupling and making code interchangable. As many powerful solutions, it is simple and elegant. Dependency inversion gives you the flexibility to create different implementations for prototyping, development, and unit tests.
Hope you find this article useful. You can find a demo project on my GitHub.
You can't perform that action at this time. You signed in with another tab or window. You signed out in another tab or…
Also, follow me on twitter for updates about different open source projects I work on:
The latest Tweets from Di (@dmytroanokhin): "Latest release of my URLImage #SwiftUI package comes with incremental…