Solid Principles - Python
Tags: solid, python
Published: 2023-02-20 07:02:14

Solid principles are a set of instructions on how to write code for production-ready software. Every software developer has to know these crucial principles to develop any application, Solid stands for

- **S** - Single responsibility principle
- **O** - Open/Closed principle
- **L** - Liskov’s substitution principle
- **I** - Interface segregation principle
- **D** - Dependency inversion principle
  
### Single Responsibility Principle :
- A class should have a single responsibility for handling processes, and if there is anything to change, it should not affect the other classes. The main objective of the single responsibility principle is to design a software component with attributes and methods that are related to each other, and it should not handle more stuff; the smaller the class, the better.
  
- It can have any number of methods if all are coupled with each other; otherwise, we need to separate those methods into separate classes.
  
    ```
    Class Game:

        def initializer_method(self):
            pass

        def draw_method(self):
            pass
        
        def running_method(self):
            pass

        def endCond_method(self):
            pass

    ```

- In the above example, we have to decouple all of the methods in the game class since some of them (draw, running) are not related to each other.


### Open-Closed Principle :
- Open/closed principle is all about open for extension but closed for modification.
It is co-related to the single responsibility principle. The code which we are writing should be capable of adding extra features without breaking the existing code.

    ```
    def read_file(file):
        return open(file, "r").read()

    def get_text_from_url(URL):
        return text

    def count_word_occurrences(word, content):
        counter = 0
        for e in content.split():
            if word.lower() == e.lower():
                counter += 1
        return counter

    def csv_reader(file):
        return text

    ```

- In the above example, we are counting the number of words in a file by reading through the file as well as from the web. What happens when we want to read a file from another resource, like a CSV file? We can create a function to read CSV data and return the text as above to the count_word function.


### Liskov’s Substitution Principle :

It states that objects of a parent class should be replaceable with objects of its subclasses without breaking the existing code/application.
- For example, Consider the below one mixer class is inheriting the kitchen parent class, what if we want to add another class Owen which has to inherit from the Kitchen class, then we can call those methods alone will work but we have to use all the methods from the parent class.
    ```
    class kitchen():

        def on():
            pass

        def off():
            pass

        def set_level():
            pass
    
    class mixer(Kitchen):
        def on():
            pass

        def off():
            pass

        def set_level():
            pass
    
    class mixer(Kitchen):

        def on():
            pass

        def off():
            pass

    ```

- So to overcome this we can create another class to set the level of the mixer and we can inherit that class and use its methods of it.
  
    ```
    class kitchen():
        def on():
            pass
        def off():
            pass
    
    class mixer_level(kitchen):
        def set_level():
            pass

    class mixer(mixer_level):
        def on():
            pass
        def off():
            pass
        def set_level():
            pass
    
    class mixer(Kitchen):
        def on():
            pass
        def off():
            pass

    ```

### Interface Segregation Principle :

A class or function should not have all the functionalities in a single method; instead, they should be separated.

- In the example below, we force the function to read data from both the URL and the file; however, what if the class that invokes this function does not need to fetch data from the URL? In that case, we must create a separate class for that functionality. If the class needs to read data from both, they can initialise objects for both classes and invoke the main function. 
  
    ```
    def get_text_from_url(URL):
        pass

    def count_word_occurrences(word, content):
        file_data=open(file, "r").read()
        url_data = get_text_from_url(URL)
        counter = 0
        for e in content.split():
            if word.lower() == e.lower():
                counter += 1
        return counter

    ```

### Dependency Inversion :
This principle states that higher-level modules should not depend on lower-level modules. Instead, both should depend on abstractions. 
In the below example, Food, production is like the base class and if we want to add another food we can create another class and inherit from the food class without breaking it. In this way low  and high level classes is depending on the abstract class FOOD.

    ```
    class FOOD:
        def bake(self): 
            pass
        def eat(self): 
            pass

    class Bread(FOOD):
        def bake(self):
            print("Bread was baked")
        def eat(self):
            print("Bread was eaten")


    class Production:
        def __init__(self, food):
            self.food = food 

        def produce(self):
            self. food.bake() 

        def consume(self):
            self. food.eat()  

    ProduceBread = Production(Bread())
    ```


### References
- https://medium.com/carbon-consulting/solid-principles-with-python-245e45f9b1f8
- https://www.youtube.com/watch?v=ZSAXFDNPcIg