S.O.L.I.D Principles
Atchukola Naresh
Posted on May 15, 2023
SOLID Principles
These are the oldest principles but we are still using these priciples due to maintains's
OO systems improve reusabulity and easy testing.
S.O.L.I.D
- SRP - Single responsibility principle
- DIP - Dependency inversion principle
- OCP - Open/closed principle
- LSP - Liskov substitution principle
- ISP - Interface segregation principle
SRP - Single responsibility principle:
A class should have only one responsibilty all the methods should be associated with the
that responsibility.
example:
class Email:
def __init__(self, subject, body, sender, recipient):
self.subject = subject
self.body = body
self.sender = sender
self.recipient = recipien
class EmailSender:
def send_email(self, email):
# Code to send the email using the SMTP server
pass
class EmailSaver:
def save_email(self, email):
# Code to save the email to a database
pass
In this example, we have separate classes for each responsibility: Email
for storing email data, EmailSender
for sending emails, and EmailSaver
for saving emails to a database. Each class has a single responsibility, making the system easier to understand, modify, and maintain.
OCP - Open/closed principle :
The Open-Closed Principle (OCP) is a fundamental principle in object-oriented programming that states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. In other words, the behavior of a system should be extendable without modifying its existing code.
from abc import ABC, abstractmethod
# Abstract class representing a shape
class Shape(ABC):
@abstractmethod
def area(self):
pass
# Concrete implementation of a Rectangle shape
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
# Concrete implementation of a Circle shape
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
# Function that calculates the sum of areas for multiple shapes
def calculate_total_area(shapes):
total_area = 0
for shape in shapes:
total_area += shape.area()
return total_area
, if we want to add a Triangle
shape, we can create a new class Triangle
that inherits from Shape
and provides its own implementation of the area()
method. We can then pass an instance of Triangle
to the calculate_total_area()
function, and it will work seamlessly without modifying the existing code.
LSP - Liskov substitution principle:
The Liskov Substitution Principle (LSP) is a principle in object-oriented programming that states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. In other words, if a program is designed to work with a certain type of object, it should also work correctly with any subtype of that object.
class Animal:
def make_sound(self):
pass
class Dog(Animal):
def make_sound(self):
return "Woof!"
class Cat(Animal):
def make_sound(self):
return "Meow!"
def animal_sounds(animals):
for animal in animals:
print(animal.make_sound())
The animal_sounds()
function demonstrates the Liskov Substitution Principle. It takes a list of Animal
objects and calls the make_sound()
method on each object. By designing the program to work with the Animal
superclass, it can also work correctly with any subtype of Animal
, such as Dog
or Cat
.
animals = [Dog(), Cat()]
animal_sounds(animals)
The animal_sounds()
function doesn't need to know the exact type of each object in the list. It treats them all as Animal
objects and calls the make_sound()
method, which is overridden in the subclasses. As a result, it will print "Woof!" and "Meow!" without any issues.
ISP - Interface segregation principle:
The Interface Segregation Principle (ISP) is a principle in object-oriented programming that states that clients should not be forced to depend on interfaces they do not use. It suggests that classes should have narrowly focused interfaces, tailored to the specific needs of the clients that use them, rather than having large, bloated interfaces that encompass more functionality than necessary.
from abc import ABC, abstractmethod
# Interface for a document
class Document(ABC):
@abstractmethod
def open(self):
pass
@abstractmethod
def close(self):
pass
# Interface for a printable document
class Printable(ABC):
@abstractmethod
def print_document(self):
pass
# Interface for a scannable document
class Scannable(ABC):
@abstractmethod
def scan_document(self):
pass
# Implementation of a Word document
class WordDocument(Document, Printable):
def open(self):
print("Opening Word document")
def close(self):
print("Closing Word document")
def print_document(self):
print("Printing Word document")
# Implementation of a PDF document
class PdfDocument(Document, Printable, Scannable):
def open(self):
print("Opening PDF document")
def close(self):
print("Closing PDF document")
def print_document(self):
print("Printing PDF document")
def scan_document(self):
print("Scanning PDF document")
# Client code that only depends on the required interfaces
def print_document(printable):
printable.print_document()
def scan_document(scannable):
scannable.scan_document()
The client code demonstrates the usage of the specific interfaces. The print_document()
function only depends on the Printable
interface, allowing it to print any document that implements that interface. Similarly, the scan_document()
function only depends on the Scannable
interface, allowing it to scan any document that implements that interface.
DIP - Dependency inversion principle :
The Dependency Inversion Principle (DIP) is a principle in object-oriented programming that states that high-level modules should not depend on low-level modules. Instead, both should depend on abstractions. It suggests that the dependencies between modules should be based on abstractions rather than concrete implementations.
from abc import ABC, abstractmethod
# Abstract class representing a notifier
class Notifier(ABC):
@abstractmethod
def send_notification(self, message):
pass
# High-level module that depends on the Notifier abstraction
class NotificationService:
def __init__(self, notifier):
self.notifier = notifier
def send_notification(self, message):
self.notifier.send_notification(message)
# Low-level module that implements the Notifier abstraction
class EmailNotifier(Notifier):
def send_notification(self, message):
print(f"Sending email notification: {message}")
# Low-level module that implements the Notifier abstraction
class SMSNotifier(Notifier):
def send_notification(self, message):
print(f"Sending SMS notification: {message}")
By having the NotificationService
depend on the Notifier
abstraction rather than concrete implementations, we adhere to the Dependency Inversion Principle. The NotificationService
is not tightly coupled to specific notifiers (e.g., EmailNotifier
or SMSNotifier
), but instead depends on the abstraction provided by the Notifier
class. This allows for flexibility and extensibility in the system. We can introduce new notifiers by creating classes that implement the Notifier
interface, and the NotificationService
will work with them without any modifications.
email_notifier = EmailNotifier()
sms_notifier = SMSNotifier()
notification_service = NotificationService(email_notifier)
notification_service.send_notification("Hello, this is an email notification")
notification_service = NotificationService(sms_notifier)
notification_service.send_notification("Hello, this is an SMS notification")
Posted on May 15, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.