The acronym originated by Robert C. Martin, and it stands for five principles of Object-Oriented Programming. These are a set of rules for certain design patterns and software architecture.
The SOLID principles are:
The Single-Responsibility Principle (SRP)
The Open-Closed Principle (OCP)
The Liskov Substitution Principle (LSP)
The Interface Segregation Principle (ISP)
The Dependency inversion Principle (DIP)
The Single-responsibility principle (SRP)
“A class should have one, and only one, reason to change”
- Every component of our code should have only one responsibility.
class User:
def __init__(self, name, email, password):
self.name = name
self.email = email
self.password = password
def login(self):
# Some code to authenticate user
return True
def change_password(self, new_password):
# Some code to change user's password
return True
Here, User class is responsible for authentication from login and password management through change_password method.
This violates SRP because the class is responsible for two distinct tasks.
class UserAuthenticator:
def __init__(self, user):
self.user = user
def login(self):
# Some code to authenticate user
return True
class PasswordManager:
def __init__(self, user):
self.user = user
def change_password(self, new_password):
# Some code to change user's password
return True
Now we have separated the responsibilities, each class is now focused on a single task.
This makes code easier to understand and maintain.
The Open–closed principle (OCP)
“Software entities … should be open for extension but closed for modification”
- If we need to add a new functionality or method, we don't need to modify the class, rather we should be able to extend it.
class checkShape:
def handle_shape(self, shape):
if request.type == 'Circle':
# Handle request type Circle
elif request.type == 'Rectangle':
# Handle request type Rectangle
elif request.type == 'Parallelogram':
# Handle request type Parallelogram
else:
# Handle other request types
Now, suppose we want to check for Triangle, we would need to modify if else ladder by adding another elif statement for Triangle.
This violates OCP.
Better solution would be to use polymorphism and create separate classes for each request type that inherit from a common abstract class or interface.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def process_shape(self):
pass
class RequestCircle(Shape):
def process_shape(self):
# Handle request type Circle
class RequestRectangle(Shape):
def process_shape(self):
# Handle request type Rectangle
class RequestParallelogram(Shape):
def process_shape(self):
# Handle request type Parallelogram
class checkShape:
def __init__(self, request):
self.request = request
def handle_shape(self):
self.request.process_request()
The Liskov substitution principle (LSP)
“Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it”
“Derived classes must be substitutable for their base classes”.
If a subclass redefines a function also present in the parent class, a client-user should not be noticing any difference in behaviour, and it is a substitute for the base class.
If a subclass violates the structure of Parent class, this would result in violation of LSP.
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, side):
self.width = self.height = side
The
Squareclass sets both the width and height to the same value, which means that theareamethod will return an incorrect value for rectangles.We can change the design to ensure that
SquareandRectanglehave a common interface but different implementations.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
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 Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
-
RectangleandSquarecan both inherit from the common abstractShapeclass and implement theareamethod in a way that is consistent with their behaviour.
The Interface Segregation Principle (ISP)
“Many client-specific interfaces are better than one general-purpose interface”
A class should only have the interface needed (SRP) and avoid methods that won’t work or that have no reason to be part of that class.
This problem arises, primarily, when, a subclass inherits methods from a base class that it does not need.
class Vehicle:
def flight_speed(self):
pass
def sail_speed(self):
pass
def speed(self):
pass
def acceleration(self):
pass
def brake(self):
pass
class Car(Vehicle):
# class would inherit all methods which might not be necessary
Vehicle interface defines 3 methods and forces Car class to use all 3 methods.
Car class might not require other methods defined in the Vehicle, which could be used by other classes like Ship and Aeroplanes.
Splitting the
Vehicleinterface into smaller interfaces that group together related methods. Then, theCarclass can inherit from only the interfaces that it needs to implement.
class LandVehicle:
def speed(self):
pass
class AirVehicle:
def flightspeed(self):
pass
class WaterVehicle:
def sailspeed(self):
pass
class Acceleration(self):
def acceleration(self):
pass
def brake(self):
pass
class Car(LandVehicle, Acceleration):
def speed(self):
# implementation of speed method
pass
def accelerate(self):
# implementation of accelerate method
pass
def brake(self):
# implementation of brake method
pass
class Aeroplane(AirVehicle, Acceleration):
pass
# define the methods from both classes
- This way class Car would inherit only those interfaces it needs to implement.
# The Dependency Inversion Principle (DIP)
“Abstractions should not depend on details. Details should depend on abstraction. High-level modules should not depend on low-level modules. Both should depend on abstractions”
class Engine:
def start(self):
print("Engine started")
class Vehicle:
def __init__(self):
self.engine = Engine()
def start(self):
self.engine.start()
The
Vehicleclass depends directly on the low-levelEngineclass to start the vehicle's engine. This violates the Dependency Inversion Principle because theVehicleclass depends directly on a low-level module.Dependency injection can be used to invert the dependencies and decouple the high-level
Vehicleclass from the low-levelEngineclass.
class Engine:
def start(self):
print("Engine started")
class Vehicle:
def __init__(self, engine: Engine):
self.engine = engine
def start(self):
self.engine.start()
We inject the
engineobject into theVehicleconstructor as a dependency.This way, the
Vehicleclass is decoupled from the specific implementation of the engine and can work with any implementation that conforms to a common interface. This meets the Dependency Inversion Principle.
class ElectricEngine(Engine):
def start(self):
# implementation of start method for electric engine
print("Electric Engine")
class FuelEngine(Engine):
def start(self):
print("Fuel Engine")
electric_engine = ElectricEngine()
fuel_engine = FuelEngine()
vehicle = Vehicle(electric_engine)
vehicle = Vehicle(fuel_engine)
vehicle.start()
We can now create an
ElectricEngineorGasEngineclass that conforms to this interface and pass it to theVehicleconstructor.The
Vehicleclass is decoupled from the specific implementation of the engine and can work with any implementation that conforms to theengineinterface.
Top comments (0)