SOLID Principles
Published: 2023-02-20 06:31:29

The SOLID principles are set of five design principles that are commonly used in object-oriented programming.

If you follow, these principles you can improve the reliability of your code by working on its structure and its logical consistency.

They are:
1. Single Responsibility Principle (SRP)
2. Open/Closed Principle (OCP)
3. Liskov Substitution Principle (LSP)
4. Interface Segregation Principle (ISP)
5. Dependency Inversion Principle (DIP)


### Single Responsibility Principle (SRP)

A class should have only one reason to change.In python, you can apply the SRP by creating small, focused classes that do one thing well.

In the below example, we have two classes that have single responsibility.
The Calculator class handles mathematical operations, and the CalculatorPrinter class handles printing the results.

```{python}
class Calculator:

    def add(self, a, b):
        return a + b;
    
    def sub(self, a, b):
        return a - b;

class CalculatorPrinter:

    def print_result(self, result):
        print("The result is ", result)
```

### Open/Closed Principle (OCP)

A class should be open for extension but closed for modification. In python we can apply OCP by using inheritance or composition to add new functionality to a class instead of modifying it's existing code.

```{python}
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
```

In this example, we have a base Shape class that defines an area method. We also have two subclasses, Rectangle and Circle, that extends the Shape class and implement the area method.

### Liskov Substitution Principle (LSP)

Subtypes should be substitutable for their base types.
In python, we can apply the LSP by ensuring that subclasses can be used in place of their parent classes without caysing unexpected behaviour.

```{python}
class Bird:

    def fly(self):
        print("Flying")
        pass

class Penguin(Bird):

    def fly(self):
        raise Exception("I can't fly")
    
def do_fly(bird):
    bird.fly()
```

In this example, we have a Penguin subclass that overrides the fly method of it's parent class Bird.
We can pass an instance of Penguin to a function that excepts a Bird instance and the fly method of Penguin class will be called.

### Interface Segregation Principle (ISP)

Clients should not depend on interfaces they do not use.In python, you can  ISP by creating small focused interfaces that contain only the methods properties that a client needs.

In this example, we have three interfaces, Animal, Carnivore, and Herbivore that contains only the methods and properties that a client needs. We also have two subclasses, Lion and Giraffe that implements the methods of their respective interfaces.

```{python}
class Animal:

    def eat(self):
        pass

class Carnivore(Animal):

    def hunt(self):
        pass

class Herbivore(Animal):

    def graze(self):
        pass

class Lion(Carnivore):

    def hunt(self):
        print("Lion is hunting")

class Giraffe(Herbivore):

    def graze(self):
        print("Giraffe is grazing")
```

### Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules and both should not depend on abstractions. In python, we can apply DIP by using dependency injection to separate the dependencies of a class from its implementation.

For example, instead of creating an instance of a database class inside another class, you can inject the database instance into the class's constructor or method. This way, the class depends on a abstraction instead of a concrete implementation, making it more flexible and easier to test.

```{python}
class Database:
    def __init__(self, db_url):
        self.db_url = db_url

    def query(self,sql):
        pass

class UserDAO:
    def __init__(self, db):
        self.db = db

    def get_user(self, user_id):
        pass


db = Database("mysql://localhost")
user_dao = UserDAO(db)
```

### References:
[SOLID Coding in python](https://towardsdatascience.com/solid-coding-in-python-1281392a6a94)