SOLID Principle
Published: 2023-05-15 12:11:46

**Introduction :**

The SOLID principle was introduced by Robert C. Martin, also known as Uncle Bob and it is a coding standard in programming. The SOLID principles are a set of five design principles that are commonly used in object-oriented programming to create more maintainable and scalable software. This principle is an acronym of the five principles which is given below…  
  
**1) Single Responsibility Principle (SRP)
2) Open/Closed Principle (OCP)
3) Liskov’s Substitution Principle (LSP) 
4) Interface Segregation Principle (ISP)
5) Dependency Inversion Principle (DIP)**

The SOLID principle helps in reducing tight coupling. Tight coupling means a group of classes are highly dependent on one another which you should avoid in your code.
  
## 1. Single Responsibility Principle :  
This principle states that “a class should have only one reason to change” which means every class should have a single responsibility or single job or single purpose. Take the example of developing software. The task is divided into different members doing different things as front-end designers do design, the tester does testing and backend developer takes care of backend development part then we can say that everyone has a single job or responsibility.

Most of the time it happens that when programmers have to add features or new behavior they implement everything into the existing class which is completely wrong. It makes their code lengthy, complex and consumes time when later something needs to be modified. Use layers in your application and break God classes into smaller classes or modules.
For example :-
```
class Logger:  
	def log(self, message):  
		with open('log.txt', 'a') as f:
			f.write(message + '\n')  
```
In this example, the Logger class has only one responsibility, which is to log messages to a file. It doesn't have any other responsibilities, such as sending emails or writing to a database.

## 2. Open/Closed Principle:

This principle states that “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification” which means you should be able to extend a class behavior, without modifying it. For example :-
```
class Animal:
	def speak(self):
		pass

class Dog(Animal):
		def speak(self):
			return "Woof!"
  
class Cat(Animal):
		def speak(self):
			return "Meow!"
```
In this example, the Animal class is closed for modification, but open for extension. You can add new subclasses (such as Dog and Cat) without modifying the Animal class.
## 3. Liskov’s Substitution Principle:  
The principle was introduced by Barbara Liskov in 1987 and according to this principle “Derived or child classes must be substitutable for their base or parent classes“. This principle ensures that any class that is the child of a parent class should be usable in place of its parent without any unexpected behavior.  
``` 
class Rectangle:  
	def __init__(self, width, height):  
		self.width = width  
		self.height = height  
  
	def area(self):  
		return self.width * self.height  
  
class Square(Rectangle):  
	def __init__(self, size):  
		self.width = size  
		self.height = size  
  
	def area(self):  
		return self.width * self.height
```
One of the classic examples of this principle is a rectangle having four sides. A rectangle’s height can be any value and width can be any value. A square is a rectangle with equal width and height. So we can say that we can extend the properties of the rectangle class into square class. In order to do that you need to swap the child (square) class with parent (rectangle) class to fit the definition of a square having four equal sides but a derived class does not affect the behavior of the parent class so if you will do that it will violate the Liskov Substitution Principle.  
  
## 4. Interface Segregation Principle:  
This principle is the first principle that applies to Interfaces instead of classes in SOLID and it is similar to the single responsibility principle. It states that “do not force any client to implement an interface which is irrelevant to them“.  
This principle states that a class should not be forced to depend on methods it does not use. In other words, a class should only depend on the methods it actually needs. For example :
```
class Machine:  
	def print_document(self):  
		pass
	def scan_document(self):  
		pass  
		
class Printer(Machine):  
	def print_document(self):  
		print("Printing document...")

class Scanner(Machine):  
	def scan_document(self):  
		print("Scanning document...")  
  
class Copier(Machine):  
	def __init__(self):  
		self.printer = Printer()  
		self.scanner = Scanner()  
  
	def print_document(self):  
		self.printer.print_document()  
  
	def scan_document(self):  
		self.scanner.scan_document()
```
In this example, the Copier class only depends on the methods it needs from the Printer and Scanner classes. It doesn't need the scan_document method from the Printer class or the print_document method from the Scanner class.  
  
## 5. Dependency Inversion Principle:  
Two key points are here to keep in mind about this principle :  
  
1. High-level modules/classes should not depend on low-level modules/ classes. Both should depend upon abstractions.  
2. Abstractions should not depend upon details. Details should depend upon abstractions.  
```
Example:
Violation of DIP

class BackendDeveloper:
    """This is a low-level module"""
    @staticmethod
    def python():
        print("Writing Python code")
class FrontendDeveloper:
    """This is a low-level module"""
    @staticmethod
    def javascript():
        print("Writing JavaScript code")
class Project:
    """This is a high-level module"""
    def __init__(self):
        self.backend = BackendDeveloper()
        self.frontend = FrontendDeveloper()
    def develop(self):
        self.backend.python()
        self.frontend.javascript()
        return "Develop codebase"
project = Project()
print(project.develop())
```

The project class is a high-level module and backend & frontend are the low-level modules. In this example, we found that the high-level module depends on the low-level module. Hence this example are violated the Dependency Inversion Principle. Let’s solve the problem according to the definition of DIP.
Solution:
```
Example 1

class BackendDeveloper:
   """This is a low-level module"""
   def develop(self):
       self.__python_code()
   @staticmethod
   def __python_code():
       print("Writing Python code")
class FrontendDeveloper:
   """This is a low-level module"""
   def develop(self):
       self.__javascript()
   @staticmethod
   def __javascript():
       print("Writing JavaScript code")
class Developers:
   """An Abstract module"""
   def __init__(self):
       self.backend = BackendDeveloper()
       self.frontend = FrontendDeveloper()
   def develop(self):
       self.backend.develop()
       self.frontend.develop()
class Project:
   """This is a high-level module"""
   def __init__(self):
       self.__developers = Developers()
   def develops(self):
       return self.__developers.develop()
project = Project()
print(project.develops())
```
**References**
https://www.freecodecamp.org/news/solid-principles-explained-in-plain-english/

https://www.baeldung.com/solid-principles

https://www.pythontutorial.net/python-oop/python-open-closed-principle/

https://medium.com/@alex24dutertre/square-rectangle-and-the-liskov-substitution-principle-ee1eb8433106

https://www.bmc.com/blogs/solid-design-principles/