The SOLID principles are a set of guidelines that aim to make software design more understandable, flexible, and maintainable. These principles were first described by Robert C. Martin, also known as Uncle Bob, in his book "Agile Software Development, Principles, Patterns, and Practices."
SOLID is an acronym that stands for the following five principles:
1.Single Responsibility Principle (SRP)
2.Open-Closed Principle (OCP)
3.Liskov Substitution Principle (LSP)
4.Interface Segregation Principle (ISP)
5.Dependency Inversion Principle (DIP)
Below, we'll go through each of these principles and provide a Python code example to illustrate how they can be applied in practice.
Single Responsibility Principle (SRP)
The Single Responsibility Principle states that a class should have only one reason to change. In other words, a class should have a single, well-defined responsibility.
This helps to make a system more flexible and maintainable, because changes to a class only need to be made in one place, rather than having to be made in multiple places throughout the system.
Here's an example of how the Single Responsibility Principle can be applied in Python:
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def get_name(self):
return self.name
def get_email(self):
return self.email
class UserManager:
def __init__(self):
self.users = []
def add_user(self, user):
self.users.append(user)
def get_users(self):
return self.users
In this example, the User class has a single responsibility: to store and retrieve information about a user. The UserManager class, on the other hand, has the responsibility of managing a list of users. This separation of responsibilities makes it easier to understand and maintain the code.
Open-Closed Principle (OCP)
The Open-Closed Principle states that a class should be open for extension, but closed for modification. This means that a class should be designed in such a way that it can be extended to add new functionality, without having to modify the existing code.
Here's an example of how the Open-Closed Principle can be applied in Python:
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
class AreaCalculator:
def __init__(self, shapes):
self.shapes = shapes
def calculate_area(self):
total_area = 0
for shape in self.shapes:
total_area += shape.area()
return total_area
In this example, the Shape class is an abstract class that defines the area method. The Rectangle and Circle classes are concrete implementations of the Shape class, and they each have their own implementation of the area method.
The AreaCalculator class is responsible for calculating the total area of a list of shapes. It does this by iterating over the list of shapes and calling the area method on each one.
This design follows the Open-Closed Principle, because if we want to add a new type of shape, we can simply create a new class that extends the Shape class and provides its own implementation of the area method. We don't have to modify the AreaCalculator class at all.
Liskov Substitution Principle (LSP)
The Liskov Substitution Principle states that a subclass should be able to be used in place of its superclass, without causing any issues. In other words, a subclass should be a substitutable implementation of its superclass.
Here's an example of how the Liskov Substitution Principle can be applied in Python:
class Vehicle:
def start(self):
pass
def stop(self):
pass
class Car(Vehicle):
def start(self):
print("Starting car")
def stop(self):
print("Stopping car")
class Motorcycle(Vehicle):
def start(self):
print("Starting motorcycle")
def stop(self):
print("Stopping motorcycle")
def test_vehicle(vehicle):
vehicle.start()
vehicle.stop()
car = Car()
motorcycle = Motorcycle()
test_vehicle(car)
test_vehicle(motorcycle)
In this example, the Vehicle class is an abstract class that defines the start and stop methods. The Car and Motorcycle classes are concrete implementations of the Vehicle class, and they each have their own implementation of the start and stop methods.
The test_vehicle function takes a Vehicle object as an argument and calls the start and stop methods on it. Because the Car and Motorcycle classes are substitutable implementations of the Vehicle class, we can pass either a Car object or a Motorcycle object to the test_vehicle function, and it will work correctly.
Interface Segregation Principle (ISP)
The Interface Segregation Principle states that clients should not be forced to depend on interfaces they don't use. In other words, a client should not have to depend on methods that it doesn't use.
Here's an example of how the Interface Segregation Principle can be applied in Python:
class Animal:
def eat(self):
pass
def sleep(self):
pass
def speak(self):
pass
def fly(self):
pass
class Dog(Animal):
def eat(self):
print("Eating")
def sleep(self):
print("Sleeping")
def speak(self):
print("Barking")
class Cat(Animal):
def eat(self):
print("Eating")
def sleep(self):
print("Sle
In this example, the Animal class defines four methods: eat, sleep, speak, and fly. These methods may not be applicable to all animals. For example, not all animals can fly.
The Dog and Cat classes are concrete implementations of the Animal class, and they each implement the methods that are applicable to them. The Dog class implements the eat, sleep, and speak methods, but not the fly method, because dogs cannot fly. Similarly, the Cat class implements the eat, sleep, and speak methods, but not the fly method, because cats cannot fly.
This design follows the Interface Segregation Principle, because the Dog and Cat classes are not forced to depend on the fly method, which they do not use.
Dependency Inversion Principle (DIP)
The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Instead, both should depend on abstractions.
Here's an example of how the Dependency Inversion Principle can be applied in Python:
class Database:
def save(self, data):
pass
def load(self):
pass
class User:
def __init__(self, database):
self.database = database
def save(self):
data = {"name": self.name, "email": self.email}
self.database.save(data)
def load(self):
data = self.database.load()
self.name = data["name"]
self.email = data["email"]
In this example, the Database class is a low-level module that provides methods for saving and loading data. The User class is a high-level module that depends on the Database class to save and load user data.
However, rather than depending directly on the Database class, the User class depends on an abstraction (i.e., the database attribute). This means that the User class can work with any implementation of the Database class, as long as it provides the necessary save and load methods.
This design follows the Dependency Inversion Principle, because the high-level User class depends on an abstraction (the database attribute), rather than on a specific implementation of the Database class.
I hope this has been a helpful overview of the SOLID principles and how they can be applied in Python. Applying these principles can help to make your software design more understandable, flexible, and maintainable.
Thank you for reading my article! If you enjoyed it and would like to support my work, please consider buying me a coffee at Buy Me a Coffee. You can also learn more about me and my work by visiting my Giasuddin Bio and following me on LinkedIn and Twitter. Thank you for your support!
Top comments (2)
Gias, Superb post.
Well written post.
Few suggestions:
You could have used ABCs in python to represent abstract classes abc
The dependency inversion principle's example could have been better as it's not clearly highlighting the point.