SOLID Principles

sanadiqbal

sanadiqbal

Posted on February 20, 2023

SOLID Principles

The SOLID principles are the five important principles of Object Oriented class design.Put simply, they are set of rules that every developer should follow while designing class structures.The main purpose of SOLID principles is to create understandable,readable and testable code that many developers can collaboratively work on.
The five SOLID principles are:

  1. Single Responsibility principle
  2. Open-Closed principle
  3. Liskov Substitution principle
  4. Interface Segregation principle
  5. Dependency Inversion principle

Let's look at these principles one by one with Python codes.

Single Responsibility Principle

The Single Responsibility principle states that every class, module and function in a program should have one single responsibility/purpose in that program and therefore should have only one reason to change.

Let's look at a code :

class Employeee:

    def send_email(self):
        pass
    def employee_performance(self):
        pass
Enter fullscreen mode Exit fullscreen mode

The above code violates the Single responsibility principle as the employee class performs two responsibilities, sending email and assessing employee's performance.
Let's fix this code so that it follows SRP.

class EmployeeEmail:
   def send_email(self): 
       pass

class Employeeperformace:
   def employee_performance(self):
       pass
Enter fullscreen mode Exit fullscreen mode

Open Closed Principle

The Open Closed Principle states that the classes should be open for extension but closed for modification. This means that the class should be made in such a way that their core functionality can be extended to other classes without altering the initial class's source code.

class PersonStorage:

    def savetodatabase(self, person):
        print('saved the {} to database'.format(person))

    def savetojson(self, person):
        print('saved to {} json '.format(person))


if __name__ == '__main__':
    p1 = 'Sam'
    storage = PersonStorage()
    storage.savetodatabase(p1)

Enter fullscreen mode Exit fullscreen mode

In the above code suppose we want to save the person to XML file. It's not possible without modifying the class PersonStorage. So the above class is open to modification but closed to extension. Hence it does not follow OCP.

from abc import ABC, abstractmethod


class PersonStorage(ABC):

    @abstractmethod
    def save(self, person):
        pass


class PersonDB(PersonStorage):

    def save(self, person):
        print(f'Save the {} to database'.format(person))


class PersonJSON(PersonStorage):

    def save(self, person):
        print(f'Save the {} to a JSON file'.format(person))


class PersonXML(PersonStorage):

    def save(self, person):
        print(f'Save the {} to a XML file'.format(person))
Enter fullscreen mode Exit fullscreen mode

To solve the problem, we first created an abstract class PersonStorage and create an abstract method save in it.
After that we create a class for each saving type which inherits the abstract class PersonStorage. After that we override the save function everytime we create a new class.
Now our code follows OCP.

Liskov Substitution Principle

Liskov principle states that the object of a class should be able to be substituted by the object of it's subclass without
changing the code's correctness. This means that when an instance of a class is passed to another class, the child class must have a use case for all the properties and behaviour of the parent class .Let's look at some codes to understand this.

class Notification(ABC):
    @abstractmethod
    def notify(self, message, email):
        pass


class Email(Notification):
    def notify(self, message, email):
        print(f'Send {message} to {email}')


class SMS(Notification):
    def notify(self, message, phone):
        print(f'Send {message} to {phone}')
Enter fullscreen mode Exit fullscreen mode

The above example violates Liskovs Substitution principle as the as the notify method in SMS class takes phone as input instead of email.

from abc import ABC, abstractmethod

class Notification(ABC):
    @abstractmethod
    def notify(self, message):
        pass
class Email(Notification):
    def __init__(self, email):
        self.email = email

    def notify(self, message):
        print(f'Send "{message}" to {self.email}')
class SMS(Notification):
    def __init__(self, phone):
        self.phone = phone

    def notify(self, message):
        print(f'Send "{message}" to {self.phone}')
Enter fullscreen mode Exit fullscreen mode

To conform to the Liskov principle first we removed email as an argument from Notification abstract class. Then we made init method in both email and SMS class taking email and SMS respectively as the arguments.

Interface Segregation Principle

Interface Seggregation Principle states that the interface of a program should be split in such a way that the user has access to only the methods of their needs and they don't have to deal with unnecessary or complex methods which are of no use to them.Let's understand it with the help of code.

from abc import ABC, abstractmethod

class Vehicle(ABC):
    @abstractmethod
    def go(self):
        pass
    @abstractmethod
    def fly(self)
        pass

class Car(Vehicle):
    def go(self):
        print('Going on road')

class Airplane(Vehicle):
    def go(self):
        print('Going on runway')
    def fly(self):
        print('Flying in air')
Enter fullscreen mode Exit fullscreen mode

In the above example notice that the class car has no use of the fly method. Hence Interface Segregation Principle is violated.

from abc import ABC, abstractmethod

class Movable(ABC):
    @abstractmethod
    def go(self):
        pass
class Flyable(Vehicle):
    def fly(self):
        pass

class Car(Movable):
    def go(self):
        print('Going on road')

class Airplane(Flyable):
    def go(self):
        print('Going on runway')
    def fly(self):
        print('Flying in air')
Enter fullscreen mode Exit fullscreen mode

We conformed to ISP by splitting vehicle into two interface movable and flyable.

Dependency Inversion

The Dependency Inversion Principle states that the classes in our function should depend on abstract classes rather than concrete classes.Let's understand it with the help of code.

class shape:
    def length(self):
        pass 
    def width(self):
        pass
    def area(self):
        pass

class rectangle(self):
    def length(self):
        pass
    def width(self):
        pass
    def area(self):
        pass
Enter fullscreen mode Exit fullscreen mode

The above class rectangle violates dependency inversion principle as it depends on a concrete class.

from abc import ABC,abstractmethod
class shape:(ABC)
    @abstractmethod
    def length(self):
        pass 
    @abstractmethod
    def width(self):
        pass
    @abstractmethod
    def area(self):
        pass

class rectangle(self):
    def length(self):
        pass
    def width(self):
        pass
    def area(self):
        pass
Enter fullscreen mode Exit fullscreen mode

To conform to DIP we simply make the shape class as abstract class and all of it's methods as abstract methods.

References

https://www.freecodecamp.org/news/solid-principles-explained-in-plain-english/
https://www.freecodecamp.org/news/solid-principles-single-responsibility-principle-explained/
https://www.pythontutorial.net/python-oop/python-dependency-inversion-principle/
https://www.pythontutorial.net/python-oop/python-interface-segregation-principle/
https://www.pythontutorial.net/python-oop/python-liskov-substitution-principle/
https://www.pythontutorial.net/python-oop/python-open-closed-principle/

💖 💪 🙅 🚩
sanadiqbal
sanadiqbal

Posted on February 20, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related