# SOLID principles

So I am going to start this post by saying a little history about me and what made me really curious to learn about the SOLID principles. I had an interview at a company and by their job description they needed someone with the knowledge of this. I had little time to go through and really understand it coupled with my work. Anyways the time for the interview came and I could only remember one of the principles 😂. I will tell you which of the principles I remembered as we move forward.  
So at the end of this we should have the real basic knowledge of SOLID principles and how we can apply it to our daily work life to write maintainable, scalable and readable code.

Overview -

* S - Single Responsibility Principle
    
* O - Open Closed Principle
    
* L - Liskov Substitution Principle
    
* I - Interface Segregation Principle
    
* D - Dependency Inversion Principle
    

### **Single Responsibility Principle (SRP)**

This Principle simply states that a class or object should only have one responsibility and one responsibility only and should only have one reason to change

**Example:**

❌ **Don’t do it like this**

```python
#It should never be done like this where one class handles two responsibilities
class TaskManager:
        def write_into_file(self):
            with open('example.txt', 'w') as file:
                file.write("Hello, World!\n")
                file.write("This is a second line.\n")
                lines = ["Line 1\n", "Line 2\n", "Line 3\n"]
                file.writelines(lines)

            with open('example.txt', 'a') as file:
                file.write("This line is appended.\n")

        def read_from_file(self):
            with open('example.txt', 'r') as file:
                content = file.read()
                print("Full content:")
                print(content)

            # Read file line by line
            print("\nLine by line:")
            with open('example.txt', 'r') as file:
                for line in file:
                    print(line.strip()) 

            # Read all lines into a list
            with open('example.txt', 'r') as file:
                lines = file.readlines()
                print("\nAll lines as list:")
                print(lines)
```

**Instead** **do this** ✅

```python
# Now there is a class to handle writing to a file and a class to handle reading a file
class WriteFile :
    def write_into_file(self):
        with open('example.txt', 'w') as file:
            file.write("Hello, World!\n")
            file.write("This is a second line.\n")
            lines = ["Line 1\n", "Line 2\n", "Line 3\n"]
            file.writelines(lines)

        with open('example.txt', 'a') as file:
            file.write("This line is appended.\n")
    
class ReadFile:
    def read_from_file(self):
        with open('example.txt', 'r') as file:
            content = file.read()
            print("Full content:")
            print(content)

        # Read file line by line
        print("\nLine by line:")
        with open('example.txt', 'r') as file:
            for line in file:
                print(line.strip()) 

        # Read all lines into a list
        with open('example.txt', 'r') as file:
            lines = file.readlines()
            print("\nAll lines as list:")
            print(lines)
```

### **Open Closed Principle (OCP)**

This is simply means Open to extension but closed to modification. This also has a relation with the structural design pattern where the composition of the object remains untouched but instead its functionalities can be extended using a decorator or an abstraction.

**Example:**

```python
from abc import abstractmethod,ABC

class Animal(ABC) :

    @abstractmethod()
    def reproduce(self):
        pass

class Mammal(Animal):
    def __init__(self, male, female):
        self.male = male
        self.female = female
    def reproduce(self):
        return f'{self.male}_and_{self.female}_give_birth_to_young_ones_alive'
    
class Birds(Animal):
    def __init__(self, male, female):
        self.male = male
        self.female = female
    def reproduce(self):
        return f'{self.female}_lay eggs'

class Reptiles(Animal):
    def __init__(self, male, female):
        self.male = male
        self.female = female
    def reproduce(self):
        return f'eggs_develop_and_hatch_inside_the_{self.female}'
```

### **Liskov Substitution Principle (LSP)**

This principle simply states that the child class should be able to simply replace the parent class without affecting the usual behaviour of the program. For example A parent class of bird which is expected to have a functionality to fly can easily be replaced by its child class sparrow because it has every functionality to fly, having a child penguin will be so improper as they cannot fly.

**Example:**

```python
class Bird(ABC):
    @abstractmethod()
    def fly(self):
        """All birds must be able to fly"""
        pass

class Sparrow(Bird):
    def fly(self):
        return 'flying'
```

### **Interface Segregation Principle (ISP)**

This principle simply states that Interface should not contain implementation they do not use as they should only contain implementations they use. Instead they can be separated into smaller classes.

**Example:**

```python
# Instead of doing something like this
class House:

    def treat_patients(self):
        return 'Treat sick patients'
    
    def worship_deity(self):
        return 'worship deity'
    
    def shop_items(self):
        return 'Buy a handbag'
    

# Do this
class Hospital :
    def treat_patients(self):
        return 'Treat sick patients'

class Temple :
   def worship_deity(self):
        return 'worship deity' 
   
class Supermarket :
   def shop_items(self):
        return 'Buy a handbag'
```

### **Dependency Inversion Principle (DIP)**

This principle simply states that high level module should not depend on low level module in fact both should depend on abstraction and abstraction should not depend on details instead details should be depend on abstractions. When objects are tightly coupled, like for instance a business logic and an implementation it becomes more difficult when the decision to change business logic comes. so Instead allowing both the business logic(high module) and implementation(low level) depend on abstraction it makes this change possible.

**Example:**

```python
# Without DIP
class MongoDBClient:
    def find_by_id(self, user_id):
        # Imagine querying MongoDB here
        return {"id": user_id, "name": "Sylar"}


class UserService:
    def __init__(self):
        self.db = MongoDBClient()  # tightly coupled

    def get_user(self, user_id):
        return self.db.find_by_id(user_id)


service = UserService()
print(service.get_user("123"))


# With DIP
from abc import ABC, abstractmethod

# Abstraction (contract)
class UserRepository(ABC):
    @abstractmethod
    def find_by_id(self, user_id: str):
        pass


# Low-level detail (MongoDB implementation)
class MongoUserRepository(UserRepository):
    def find_by_id(self, user_id: str):
        return {"id": user_id, "name": "Sylar (MongoDB)"}


# Another implementation (e.g., PostgreSQL)
class PostgresUserRepository(UserRepository):
    def find_by_id(self, user_id: str):
        return {"id": user_id, "name": "Victor (Postgres)"}


# High-level module (depends on abstraction, not concrete DB)
class UserService:
    def __init__(self, user_repo: UserRepository):
        self.user_repo = user_repo

    def get_user(self, user_id: str):
        return self.user_repo.find_by_id(user_id)


mongo_repo = MongoUserRepository()
postgres_repo = PostgresUserRepository()

service_mongo = UserService(mongo_repo)
service_postgres = UserService(postgres_repo)

print(service_mongo.get_user("123"))
print(service_postgres.get_user("456"))
```

### **CLOSING REMARK**

SOLID principle actually helps software engineers write clean, scalable and maintainable code, It is also of best practices following the OOP standards. It also guides the design process. I earlier mentioned what intrigued me to learn about the SOLID principles was an interview where I was asked about it. well the only one I could remember very well was the OCP 😅. And one reason was because I was very familiar with using decorators with python and also nest.js with typescript. Even though I had built applications on nest.js following this principle, I was not familiar with its theory and why I had to use them, but learning about SOLID actually got me to learn the actual use and why exactly I had to write them like that. Hope you enjoyed the post, thank you.
