Back to Basics: SOLID Principles (Dependency Inversion)

In a software development lifecycle, the decision on how accessible and flexible an object is during this object's design phase will ensure its usability, simplicity, ease of implementation, and accessibility towards making reliable software.

In this post, we will try to understand the fifth SOLID principle and how it can help us write easily extendable software applications.

Dependency Inversion Principle:

This principle suggests:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

By following this approach, it makes the interchanging of modules and classes or services simple.

Let's see how this works with an example:

class MySQLDatabase():

    def insert(self, data):
        """Logic to save the data in MySQL DB"""
        pass

    def delete(self, id):
        """Logic to delete a row from the DB"""
        pass

class CarManufacturer():
    def __init__(self):
        self._db = MySQLDatabase()

    def create_car(self, data):
        self._db.insert(data)

    def delete_car(self, car_id):
        self._db.delete()

In the above example, CarManufacturer class is dependent on MySQLDatabase class. In the future, if we want to change our database from MySQL to suppose Postgres we will have to go to all the classes that are using the MySQLDatabase class and update the code to use the new Postgres implementation.

This violates the Open-Closed Principle.

Solution?

We will use the Dependency Inversion Principle to make our low-level class i.e. MySQLDatabase to extend an abstraction and our high-level class i.e. CarManufacturer will depend on that abstraction to implement the database functions.

class Database:

    def insert(self, data):
        pass

    def delete(self, id):
        pass

class MySQLDatabase(Database):

    def insert(self, data):
        """Logic to save the data in MySQL DB"""
        pass

    def delete(self, id):
        """Logic to delete a row from the MySQL DB"""
        pass


class CarManufacturer():
    def __init__(self, DB: Database):
        self._db = DB

    def create_car(self, data):
        self._db.insert(data)

    def delete_car(self, car_id):
        self._db.delete(car_id)

Now no matter which type of database is used in the application, the CarManufacturer class can connect to the database without any problems or code changes, and the Open-Closed Principle is not violated.

By following the dependency inversion principle, we do not have to change our code, that helps with code readability and extensiveness.

This marks the end of the series, Back to Basics, each covering one SOLID principle.

12