Understanding S.O.L.I.D: Dependency Inversion Principle.

Understanding S.O.L.I.D: Dependency Inversion Principle.

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. twohourslater.jpg 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.

img1.PNG

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

SOLID principles

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 … 
    }
}

img 2.PNG

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!! ✌🏽