SOLID Principles
Published: 2023-05-15 10:14:53


* These are five principles of object oriented programming, helps in writing clean code and build good architecture.
* These are first introduced by Robert J. Martin (a.k.a Uncle Bob).
* SOLID stands for :
	* Single Responsibility Principle
	* The Open-Closed Principle
	* The  Liskov Substitution Principle 
	* Interface Segregation Principle
	* The  Dependency Inversion Principle


## Single Responsibility Principle
* Simple definition is that a Class should be responsible for only one function
```python
class Invoice:

	def calculate(self):
		# calculates total amount
	def print_invoice(self):
		# Prints invoice in given format 
	def save_invoice(self):
		# Saves to file
```


__Explanation__:
* In the above example, let's say Invoice class will calculate and prints the invoice slip
* So, here SRP is voilating.
* Because, SRP states that there should be only one reason to change or modify a class
* But in the above example, If you want to change the format of invoice printing you will have to change invoice class.
* If you want to change some business logic in calculate method again you have to change  the invoice class
* So, here same class is responsible for two things
__Soluton__:
```python
class InvoiceCalculate:
	def calculate(self):
		# calculates total amount
		
class PrintInvoice:
	def print_invoice(self):
		# Prints invoice in given format 
class SaveInvoice:
	def save_invoice(self):
		# Saves to file
``` 
* Here we are transporting three methods to three different class, as they have different functionalities

## The Open-Closed Principle
* This principle states that class should be closed for modification and open for extension
* Meaning, If you want to add fuctionality to the class you don't want to change the existing code as this will arise bugs
* Let's say if you want to save the invoice to both database and file
* You can do as follows
__Example__:
```python
class SaveInvoice:
	def save_invoice(self):
		# Saves to file
	def save_invoice_database(self):
		# Saves to database
```
* In the above code Open-Closed principle has been violated
* Because, we are changing or modifying the existing class for adding a new functionality
* So, to solve the above problem we can use abstraction
__Solution__:
* we will create SaveInvoice as a abstract class instead of concrete class and we will inherit the class to impliment the required function

```python
from abc import ABC,abstractmethod
# Base class
class SaveInvoice(ABC):
	@abstractmethod
	def save(self):
		pass
		
# class to imliment save function to save invoice to file
class SaveToFile(SaveInvoice):
	def save(self):
		# Save to file
		
# class to impliment save function to save invoice to database
class SaveToDatabase(SaveInvoice):
	def save(self):
		# Save to database
```
* In the above example we have used abstraction to overcome the problem of modifying the existing class
* Even in future if we want to save invice to someother source we can always create a class corresponding to that and impliment save method from base class

##  The  Liskov Substitution Principle 

* This principle states that Subclasses should be substitutable for their base classes
* In other words, person who is using our code should not know which object he is implimenting or using

__example__:
* This is closly related to the open closed priciple

```python
from abc import ABC,abstractmethod
class polygone(ABC):
	def area(self):
		pass
class rectangle(polygone):
	def __init__(self,a,b):
		self.a = a
		self.b = b
	def area(self):
		return self.a*self.b
class square(polygone):
	def __init__(self,l):
		self.l = l
	def area(self):
		return self.l * self.l
rect = rectangle(a,b)
print(rect.area())
print(isinstance(rect,polygone))
```
* The above example is similar to open-close principle
* Here, we can replace rect object with parent object polygone
* So, Lisko Substitution principle is followed

## Interface Segregation Principle

* The Interface Segregation Principle is about separating the interfaces.
* Keep multiple interfaces for multiple use cases

```python
from abc import ABC,abstractmethod
class sim(ABC):
	def call(self):
		pass
	def message(self):
		pass
	def network(self):
		pass
```

* In the above example although the functionalities of these methods are different they are in same abstract class.
* This will create obligation to impliment all the methods even though you want only call method

__Solution__
```python
from abc import ABC,abstractmethod
class sim_call(ABC):
	@abstractmethod
	def call(self):
		pass
class sim_message(ABC):
	@abstractmethod
	def message(self):
		pass
class sim_network(ABC):
	@abstractmethod
	def network(self):
		pass
		
```
* Here, we have used three seperate abstract classes for all these different functions

##  The  Dependency Inversion Principle

* The Dependency Inversion principle states that our classes should depend upon interfaces or abstract classes instead of concrete classes and functions.
* This is related to the open-close principle
* we can use the same example as in Open-close principle
* If we observe the given example, we have removed the SaveInvoice class which is concrete class and replaced them with abstract class to achieve DI principle


## References

* [freecodecamp](https://www.freecodecamp.org/news/solid-principles-explained-in-plain-english/)