SOLID Principles Of Programming
Published: 2023-02-20 05:12:24

# SOLID Principles Of Programming

+ SOLID is a set of five design principles that help us to write maintainable and flexible code.
+ They are, **SRP (Single Responsibility Principle)**
+ OCP **(Open-Closed Principle)**
+ LSP **(Liskov Substitution Principle)**
+ ISP **(Interface Segregation Principle)**
+ DIP **(Dependency Inversion Principle)**
<hr>

### 1. SRP (Single Responsibility Principle)

+ A class should have only a single responsibility.
+ Due to this, it should also have only a single reason to change.
  
An example of a class that violates SRP:

```py
class User:

    def __init__(self, name, email, password):
        self.name = name
        self.email = email
        self.password = password

    def login(self):
        pass
```

+ In the above example, the class User is responsible for both managing user data and implementing login.
+ We can make it adhere to SRP by making separate classes for user data and login.

```py
class UserData:

    def __init__(self, name, email, password):
        self.name = name
        self.email = email
        self.password = password

class UserManager:

    def login(self, user_data):
        pass
```
<hr>

### 2. OCP (Open-Closed Principle)

+ A class should be open for extension and closed for modification.
+ That is, we should be able to add new functionality without modifying existing code.
  
An example of a class that violates OCP:

```py
class Shape:

    def __init__(self, shape_type, dimensions):
        self.shape_type = shape_type
        self.dimensions = dimensions

    def area(self):
        if self.shape_type == 'rectangle':
            pass
        elif self.shape_type == 'circle':
            pass
```

+ In the above example, we will have to modify the existing code if we need to add a new shape.
+ We can make it adhere to OCP by making abstract classes and methods.

```py
from abc import ABC, abstractmethod

class Shape(ABC):

    def __init__(self, shape_type, dimensions):
        self.dimensions = dimensions
        
    @abstractmethod
    def area(self):
        pass
        
class Rectangle(Shape):

    def area(self):
        pass
        
class Circle(Shape):

    def area(self):
    	pass

class Triangle(Shape):

    def area(self):
    	pass
```
<hr>

### 3. LSP (Liskov Substitution Principle)

+ A child class should be able to substitute its parent class.
+ That is, any code that works on the parent class should also work on the child class.

An example of a class that violates LSP:

```py
class Animal:

    def __init__(self, name):
        self.name = name

    def eat(self):
        pass

class Bird(Animal):

    def fly(self):
        pass

class Ostrich(Bird):

    def fly(self):
        raise NotImplementedError
```

+ In the above example, Ostrich is a child class of Bird class but it raises an error when the fly method is called.
+ We can fix this method by making Ostrich a child class of Animal instead of Bird.

```py
class Animal:

    def __init__(self, name):
        self.name = name

    def eat(self):
        pass

class Bird(Animal):

    def fly(self):
        pass

class Ostrich(Animal):
    pass
```
<hr>

### 4. ISP (Interface Segregation Principle)

+ A client should not be forced to depend on methods it does not use.
+ That is, we should keep our interfaces small and focus only on functionality that the client needs.

An example of a class that violates ISP:

```py
class Document:

    def __init__(self, text):
        self.text = text

    def print(self):
        pass

    def fax(self):
        pass

    def scan(self):
        pass
```

+ In the above example, if a client only needs to print the document, they will still be forced to use the fax and scan method.
+ We can make it adhere to ISP by splitting the Document class into smaller, more focused classes.

```py
class Printable:

    def print(self):
        pass

class Faxable:

    def fax(self):
        pass

class Scanable:

    def scan(self):
        pass

class Document:

    def __init__(self, text):
        self.text = text

class Printer(Document, Printable):
    pass

class Fax(Document, Faxable):
    pass

class Scanner(Document, Scanable):
    pass
```
<hr>

###  5. DIP (Dependency Inversion Principle)

+ Higher-level modules should not depend upon lower-level modules.
+ Instead both should depend on abstractions.

An example of a class that violates DIP:

```py
class Database:

    def insert(self, data):
        pass

class User:

    def __init__(self, name, database):
        self.name = name
        self.database = database

    def save(self):
        pass
```

+ In the above example, the class user is dependent on the class Database.
+ To make it adhere to DIP, we can create an abstraction for the Database.

```py
from abc import ABC, abstractmethod

class DatabaseInterface(ABC):

    @abstractmethod
    def insert(self, data):
        pass

class Database(DatabaseInterface):

    def insert(self, data):
        pass

class User:

    def __init__(self, name, database):
        self.name = name
        self.database = database

    def save(self):
        pass
```
<hr>

### **REFERENCES**

+ [Medium Article](https://medium.com/@mohithmarisetti_58912/solid-principles-in-programming-56e3485118d7)
+ [YouTube Lecture by ArjanCodes](https://www.youtube.com/watch?v=pTB30aXS77U)