Meet John, (Hi everyone! 👋🏽) John just start working for a software company, and his first task was very simple:
- fetch user data from the local database using user-name.
he is excited to work and show up his skills, he knows the steps to establish a database connection and how to query a table, and he starts implementing the requirements immediately. John pushes his code to the GitHub, and it looks like this:
class UserRepository(private val connection: Connection) {
fun getUser(userName: String): User{
return connection.fetchUser(userName)
}
}
class Connection {
fun fetchUser(userName: String): User{
// fetching data ...
}
}
a UserRepository
class depends on a concrete class Connection
to deal with the database.
After a couple of weeks, the business requirements have changed, instead of fetching user data from a database, now we need to fetch user data from a backend API.
it’s impossible to meet these requirements without changes, so what john should do now is changing the code to communicate with the backend API and retrieve user data.
//change connection to API
class UserRepository(private val api: Api) {
fun getUser(userName: String): User{
// change getUser logic.
return api.fetchUser(userName)
}
}
class Api {
fun fetchUser(userName: String): User{
//fetching data ...
}
}
a UserRepository
class depends on a concrete class API
to deal with the backend.
What if the business asks to fetch user data from another data source?
John has to change the code again!! and breaks Open/Close principle. for the third time 🤦🏻♂️
Meet the DIP 🌟
DIP stands for Dependency Inversion Principle, which is the last letter in the
before jumping into the DI principle, let’s define what is Dependency Inversion?
Dependency: dependency is a piece of software that we depend on to accomplish a specific requirement (e.g., Utility class).
Inversion: Inversion means, the action of inverting something or the state of being inverted, in our case, we need to change the dependency relationship direction, and we will get to it.
Let’s begin 🚀
The dependency inversion principle aims to change the way we depend on low-level components,
so instead of relying on a concrete component, we should depend on abstraction.
in John’s case, we need to abstract the data fetching logic into an abstract component!
interface UserService {
fun fetchUser(userName: String): User
}
this way we can create more focused services, that deal with different data sources to fetch the user data.
class LocalUserService (private val connection: Connection): UserService {
override fun fetchUser(userName: String): User {
return connection.fetchUser(userName)
}
}
then let’s make UserRepository
depends on the UserService
interface instead of the low-level Connection
class.
class UserRepository(private val userService: UserService) {
fun getUser(userName: String): User{
return userService.fetchUser(userName)
}
}
Now UserRepository
depends on an abstract layer UserService
, to fetch user data.
and pass LocalUserService
to UserRepository
val userRepository = UserRepository(LocalUserService())
val user = userRepository.getUser("Ashraf")
LocalUserService
is a specific implementation of the UserService
Interface to fetch user data from the database.
That way, we invert the dependency from depending on a low-level component Connection
into depending on an abstraction UserService
.
it is not UserRepository
concern to know from where and how the user data is fetched.
so to respond to the new requirement, that the business needs to fetch user data from a remote server, now we can implement the UserService
interface with a more specific class to fetch user data from the backend.
class RemoteUserService: UserService {
override fun fetchUser(userName: String): User {
//fetch logic …
}
}
and just pass this new service to the UserRepository
,
val userRepository = UserRepository(RemoteUserService())
val user = userRepository.getUser("Ashraf")
what we have done?
Abstract the user fetching logic into an interface, then we made UserRepository
Inverts its dependency relation from depending on a concrete class to depending on an abstract layer.
What next?
in this quick introduction to DIP, you need to read about it more and see more examples, and keep in mind you should NOT apply this DIP blindly for every class and module, think carefully before using it.
Peace!! ✌🏽