DEV Community

NK1FC
NK1FC

Posted on

Solid Principles in Python

The SOLID Principles:

  • The Single-Responsibility Principle (SRP)
  • The Open-Closed Principle (OCP)
  • The Liskov Substitution Principle (LSP)
  • The Interface Segregation Principle (ISP)
  • The Dependency inversion Principle (DIP)

The Single-Responsibility Principle (SRP)

  • This principle state that the Class should have only one responsibility.
  • Code That violates SRP is:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        print(f"Hello, my name is {self.name} and I'm {self.age} years old.")

    def celebrate_birthday(self):
        self.age += 1
        print(f"{self.name} is now {self.age} years old!")
Enter fullscreen mode Exit fullscreen mode
  • In the above code, the Person class have two responsibility one is to greet and another one is to celebrate a birthday to follow the SRP create a different class one which handles greeting and the other handles birthday celebration.
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        print(f"Hello, my name is {self.name} and I'm {self.age} years old.")

class BirthdayCelebrator:
    def celebrate_birthday(self, person):
        person.age += 1
        print(f"{person.name} is now {person.age} years old!")
Enter fullscreen mode Exit fullscreen mode

The Open-Closed Principle (OCP)

  • This principle states that the class should be closed for modification but open for extension.
  • Code that violates OCP is:
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius ** 2

class Triangle(Shape):
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return 0.5 * self.base * self.height

class AreaCalculator:
    def calculate_area(self, shape):
        if isinstance(shape, Rectangle):
            return shape.area()
        elif isinstance(shape, Circle):
            return shape.area()
        elif isinstance(shape, Triangle):
            return shape.area()
Enter fullscreen mode Exit fullscreen mode
  • In the above code if we try to create a new class such as a hexagon we need to modify the AreaCalculator class which will violate OCP.
  • To correct the code we just need to change the method of AreaCalculator class so there will be no need to modify AreaCalculator class.
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius ** 2

class Triangle(Shape):
    def __init__(self, base, height):
        self.base = base
        self.height = heigh.t

    def area(self):
        return 0.5 * self.base * self.height

class AreaCalculator:
    def calculate_area(self, shape):
        return shape.area()
Enter fullscreen mode Exit fullscreen mode

Liskov’s Substitution Principle (LSP)

  • This principle states that the object created using the child class can replace the object created using The parent class.
  • Code that violates LSP is:
class Bird:
    def fly(self):
        print("The bird is flying")

class Penguin(Bird):
    def fly(self):
        raise Exception("I can't fly")

def make_bird_fly(bird):
    bird.fly()

bird = Bird()
make_bird_fly(bird)  # Output: The bird is flying

penguin = Penguin()
make_bird_fly(penguin)  # Output: An exception is raised
Enter fullscreen mode Exit fullscreen mode
  • In the above code, the penguin will raise the error when we call the fly method. To correct it simply do method ovveridding.
class Bird:
    def fly(self):
        print("The bird is flying")

class Penguin(Bird):
    def fly(self):
        print("I can't fly")

def make_bird_fly(bird):
    bird.fly()

bird = Bird()
make_bird_fly(bird)  # Output: The bird is flying

penguin = Penguin()
make_bird_fly(penguin) # output: I can't fly
Enter fullscreen mode Exit fullscreen mode

Interface Segregation Principle (ISP)

  • This principle states that it is better to have many small, specific interfaces, rather than one large, general-purpose interface.
  • Code that violates ISP is:
from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def eat(self):
        pass
    @abstractmethod
    def sleep(self):
        pass
    @abstractmethod
    def move(self):
        pass

class Dog(Animal):
    def eat(self):
        print("The dog is eating")

    def sleep(self):
        print("The dog is sleeping")

    def move(self):
        print("The dog is running")
Enter fullscreen mode Exit fullscreen mode
  • Above code violates the ISP as if we try to create a fish class using an animal class but it doesn't have ability to move so we need to create methods which are common in all animals such as eating, sleep, and reproduction.
  • Code that adheres ISP is:
from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def eat(self):
        pass
    @abstractmethod
    def sleep(self):
        pass
    @abstractmethod
    def reproduce(self):
        pass    
Enter fullscreen mode Exit fullscreen mode
  • above method is common in all animals hence it is the simplest interface for animals and another method will be added according to other animals in their class.

Dependency Inversion Principle (DIP)

  • This principle state that Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.
  • Code that violates DIP is:
class Shape:
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius ** 2

class Triangle(Shape):
    def __init__(self, base, height):
        self.base = base
        self.height = heigh.t

    def area(self):
        return 0.5 * self.base * self.height

class AreaCalculator:
    def calculate_area(self, shape):
        return shape.area()
Enter fullscreen mode Exit fullscreen mode
  • Code that adheres DIP is:
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius ** 2

class Triangle(Shape):
    def __init__(self, base, height):
        self.base = base
        self.height = heigh.t

    def area(self):
        return 0.5 * self.base * self.height

class AreaCalculator:
    def calculate_area(self, shape):
        return shape.area()
Enter fullscreen mode Exit fullscreen mode

References

Top comments (0)