D — (Dependency Inversion) From SOLID principles 5/5

By Published On: March 13, 2023Categories: Development

Hello Folks,

I hope you guys are writing clean code. I am here to take few precious minutes from your busy life to present my opinion on one of the most beautiful principles that are very important in developers’ life whenever they think about less coupled code but before we start If you are new here I would like to check out my complete series of 4 previous articles of SOLID principles Part 1, Part 2, Part 3 and Part 4.

The Dependency Inversion principle lets talk about the bookish definition of DI

High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces)

 

This concept is very easy to understand and complex to handle sometimes when you are actually coding. Don’t worry I’ll explain you.

What are the high-level modules and low levels? Thinking modules such as libraries or packages, the high-level modules would be those that traditionally have dependencies and low levels on which they depend.

In other words, module high level would be where the action is invoked and low level where the action is performed.

Lower level modules can be described in two ways
1) Third-party libraries
2) Lower level APIs e.g. Database, Google Map, SMS API, etc

Again in this article, I’ll raise the question.

Why do we need to avoid deal with concrete classes of such APIs?

Our primary purpose is to make our code less coupled so at any given time if we are asked to make any change/replace any module of code in our app it shouldn’t break our existing higher-level modules (Usually logical layer)

I would like to quote one example from the past.
Back in 2014, I was working on a vehicle tracking app and after one year of work one day, my boss told me that google blocked our servers and we are no more permitted to use Google Maps in our apps. So we need to replace these maps with OpenStreet Maps as quickly as possible. Now my code was too tightly coupled with a third-party library that it was impossible for me to replace it without breaking anything in the code. So I had to rewrite that whole module consist of three screen to make it a DI appliance so that I can adopt any map with a minimum of effort without disturbing my high-level layer.

I hope you had an idea about the importance of this principle, lets jump into some code and shed light on it as well.

class AuthRepository(val authApi : FirebaseAuth) {

    suspend fun login(credentials : Credentials){
        try{
            //Authentication 
            authApi.signinWithEmailAndPassword(credentials.email, credentials.password)
        }catch(e : Exception){
            //Saving error logs
            val file = File("logs")
            file.appendText {
                text = e.message
            }
        }
    }
} 

Look at the above GIST you can see are dealing with a lower level/third-party API FirebaseAuth in our Repository which clearly violates our DI principle. One day if we had to change FirebaseAuth with any other auth client, it will be tough for us to refactor our code to accept our new auth handler. So to avoid this shame let me show you how we can implement abstraction to avoid coupling 🙂

class AuthRepository(val authApi : Authenticator) {

    suspend fun login(credentials : Credentials){
        try{
            //Authentication 
            authApi.login(credentials.email, credentials.password)
        }catch(e : Exception){
            //Saving error logs
            val file = File("logs")
            file.appendText {
                text = e.message
            }
        }
    }
}

//magical abbstraction
interface Authenticator {
    fun login(email : String, password : String)
}

class FirebaseAuthHandler : Authenticator {

    override login(email : String, password : String) {
        FirebaseAuth.getInstance().signinWithEmailAndPassword(email,password)
    } 
}

//Any other Auth Client
class RetrofitAuthHandler : Authenticator {

    override login(email : String, password : String) {
        Retrofit.getInstance().signinWithEmailAndPassword(email,password)
    } 
}


I would like you to read this beautiful code where we solved a bad coding practice with abstraction. Now are dealing with abstraction to achieve authorization instead of directly talking with low-level FirebaseAuth we will call abstraction to provide us with our desired functionality and we can easily replace FirebaseAuthHandler with RetrofitAuthHandler at any given point by changing a single line of code in AuthRepository.

I hope you had enough to understand this principle.

See you next time with a new topic. Happy Coding!!

Bye Bye!!

Share this article

Written by : admin

Leave A Comment

Latest Articles