DEV Community

Utsav
Utsav

Posted on • Updated on

SOLID Principles

The aim of this paper is to make the readers familiar with the concepts of SOLID principles. They are essentially a set of practices that leads to well designed software (Readable,Modular,Scalable)

Single Responsibility Principle

Basic gist :

  • Every class must have a single responsibility to fulfill
  • If a class is performing too many tasks, especially if the tasks are logically unrelated, then you would be better off writing another class.

How it plays out :

A bad example :

    #Class below is reponsilble for user properties and user database management
    class  User:
        def __init__(self, name: str):
            self.name = name

        def get_name(self) -> str:
            pass

        def save(self, user: User):
            pass
Enter fullscreen mode Exit fullscreen mode
  • If some changes are passed to the database management system, all the classes working with user properties will have to be recompiled to compensate for new changes.

The correct way :

    #Simply split the classes.
    class User:
        def __init__(self, name: str):
                self.name = name

        def get_name(self):
            pass


    class UserDB:
        def get_user(self, id) -> User:
            pass

        def save(self, user: User):
            pass
Enter fullscreen mode Exit fullscreen mode

Open/Closed principle

  • This principle states that classes should be open to extension, not to modification. If you want to add some functionality to a class, you are better off extending it, avoiding violating the Single responsibility principle.

A bad example:

    class Discount:
        def __init__(self, customer, price):
            self.customer = customer
            self.price = price

        def give_discount(self):
            if self.customer == 'fav':
                return self.price * 0.2

            if self.customer == 'vip':
                return self.price * 0.4
Enter fullscreen mode Exit fullscreen mode

The above class is responsible for giving out discounts, however modifying the class to add the vip discount condition violates the open/closed principle, instead do this:

The correct way:

    class Discount:
        def __init__(self, customer, price):
            self.customer = customer
            self.price = price

        def get_discount(self):
            return self.price * 0.2

    class VIPDiscount(Discount):
        def get_discount(self):
            return super().get_discount() * 2
Enter fullscreen mode Exit fullscreen mode

Here, we create a new class that handles the vip discount method.

Liskov Substitution principle

The objects of a sub class should be able to replace the objects of its parent class without any changes in behavior.

    class Bird():
        def fly():
            pass

    class Duck(Bird):
        pass

    class Ostrich(Bird):
        pass
Enter fullscreen mode Exit fullscreen mode

The above example is a bad implementation, Considering ostriches can't actually fly, We need our subclasses to be able to replace parent classes while still being logically consistent.

    class Bird():
        pass

    class FlyingBirds(Bird):
        def fly():
            pass

    class Duck(FlyingBirds):
        pass

    class Ostrich(Bird):
        pass
Enter fullscreen mode Exit fullscreen mode

The example above is a good implementation of the concept.

Interface Segregation principle

  • An interface can be imagined as a contract, if you decide to implement an interface, you have to do so in its entirety

  • So what if you have interfaces with too many functions, all the classes will have
    to implement all the functions even if it just needs one or two of them.

    class IShape:
        def draw(self):
            raise NotImplementedError
    
    class Circle(IShape):
        def draw(self):
            pass
    
    class Square(IShape):
        def draw(self):
            pass
    
    class Rectangle(IShape):
        def draw(self):
            pass
    

You can implement multiple interfaces, so its better to have smaller and more specific interfaces.

Dependency Injection principle

  • Imagine a scenario where you're directly instantiating classes into other classes. Everytime a class needs to be instantiated, all the classes that its dependent on will need to be instantiated as well, incurring a massive overhead.

  • This is solvable by simply giving the responsinbility of instantiation of objects to another entity.

    class AuthenticationForUser():
        def __init__(self, connector:Connector):
            self.connection = connector.connect()
    
        def authenticate(self, credentials):
            pass
        def is_authenticated(self):
            pass    
        def last_login(self):
            pass
    
    class AnonymousAuth(AuthenticationForUser):
        pass
    
    class GithubAuth(AuthenticationForUser):
        def last_login(self):
            pass
    
    class FacebookAuth(AuthenticationForUser):
        pass
    
    class Permissions()
        def __init__(self, auth: AuthenticationForUser)
            self.auth = auth #Object is injected here.
    
        def has_permissions():
            pass
    
    class IsLoggedInPermissions (Permissions):
        def last_login():
            return auth.last_log
    

The AuthenticationForUser object is passed into the constructor instead of being instantiated inside the class

Top comments (0)