SOLID Principles
These five software development principles are guidelines to follow when building software so that it is easier to scale and maintain. They were made popular by a software engineer, Robert C. Martin.

For better visual understanding: The S.O.L.I.D Principles in Pictures
The SOLID Principles
TO MAKE EASIER AND SIMPLE TO FOLLOW.
I WILL BE USING WORD
"CLASS"
FOR SIMPLICITY.THESE BELOW PRINCIPLES CAN ALSO BE APPLIED TO
"FUNCTIONS", "METHOD" OR "MODULE".
S - Single Responsibility Principle (SRP)
A
CLASS
should only have one reason to change. i.e. one responsibility
Why: If a "CLASS"
has multiple responsibilities, changes to one responsibility can unintentionally affect others, increasing the risk of bugs.
Goal: Separate behaviors so that changes in one area do not impact unrelated areas.
Example: A Report
class should only handle report generation, not saving or emailing reports. Those should be separate classes.
Violates SRP: Report CLASS handles generating, saving, and emailing.
class Report:
def generate(self):
print("Generating report...")
def save(self):
print("Saving report to disk...")
def email(self):
print("Emailing report...")
SRP-compliant: Separate classes for each responsibility
class Report:
def generate(self):
print("Generating report...")
class ReportSaver:
def save(self, report):
print("Saving report to disk...")
class ReportEmailer:
def email(self, report):
print("Emailing report...")
O - Open-Closed Principle (OCP)
A
CLASS
should be open for extension but closed for modification.
Why: Modifying existing code can introduce bugs in systems that rely on that code. Instead, extend functionality by adding new code.
Goal: Allow a class’s behavior to be extended without changing its existing code, reducing the risk of breaking other parts of the system.
Example: Add new features by creating subclasses or using interfaces, rather than altering the original class.
Violation: Adding new report formats requires modifying the existing class.
class ReportPrinter:
def print_report(self, report, format_type):
if format_type == "PDF":
print("Printing PDF report...")
elif format_type == "HTML":
print("Printing HTML report...")
Correction: Extend functionality by adding new classes, not modifying existing ones.
from abc import ABC, abstractmethod
class ReportPrinter(ABC):
@abstractmethod
def print_report(self, report):
pass
class PDFReportPrinter(ReportPrinter):
def print_report(self, report):
print("Printing PDF report...")
class HTMLReportPrinter(ReportPrinter):
def print_report(self, report):
print("Printing HTML report...")
L - Liskov Substitution Principle (LSP)
If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program.
Why: If a subclass cannot perform the same actions as its parent, it can cause unexpected bugs.
Goal: Ensure that derived classes extend the base class without changing its expected behavior.
Example: If a Coffee
class has a Cappuccino
subclass, the subclass should behave like a Coffee
In all contexts where Coffee
is expected.
Violation: A subclass breaks the expected behavior of the base class.
class Coffee:
def brew(self):
print("Brewing coffee...")
class Cappuccino(Coffee):
def brew(self):
raise Exception("Cappuccino cannot be brewed like regular coffee!")
Correction: The subclass can be used anywhere the base class is expected.
class Coffee:
def brew(self):
print("Brewing coffee...")
class Cappuccino(Coffee):
def brew(self):
print("Brewing cappuccino...")
def make_coffee(coffee: Coffee):
coffee.brew()
make_coffee(Coffee()) # Works
make_coffee(Cappuccino()) # Also works, as expected
I - Interface Segregation Principle (ISP)
Classes should not be forced to depend on methods they do not use.
Why: Large, general-purpose interfaces force classes to implement unnecessary methods, leading to bloated and fragile code.
Goal: Split large interfaces into smaller, client-specific ones so that classes only implement what they need.
Example: Instead of one big interface for all printer functions, have separate interfaces for scanning, printing, and faxing.
Violation: A single interface forces classes to implement unused methods.
class MultiFunctionPrinter:
def print(self):
pass
def scan(self):
pass
def fax(self):
pass
class SimplePrinter(MultiFunctionPrinter):
def print(self):
print("Printing...")
def scan(self):
pass # Not needed
def fax(self):
pass # Not needed
Correction: Split into smaller, focused interfaces.
class Printer:
def print(self):
print("Printing...")
class Scanner:
def scan(self):
print("Scanning...")
class Fax:
def fax(self):
print("Faxing...")
class MultiFunctionPrinter(Printer, Scanner, Fax):
pass
class SimplePrinter(Printer):
pass
D - Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on the abstraction.
Abstractions should not depend on details. Details should depend on abstractions.
Why: Direct dependencies between high-level and low-level modules make code rigid and hard to change.
Goal: Reduce coupling by introducing interfaces or abstract classes, making the system more flexible and testable.
Example: A LightSwitch
class should depend on a SwitchableDevice
interface, not directly on a LightBulb
class.
Violation: High-level class depends directly on a low-level class.
class LightBulb:
def turn_on(self):
print("LightBulb ON")
def turn_off(self):
print("LightBulb OFF")
class LightSwitch:
def __init__(self, LightBulb):
self.bulb = LightBulb
def operate(self, on):
if on:
self.bulb.turn_on()
else:
self.bulb.turn_off()
Correction: Depending on an abstraction/interface.
from abc import ABC, abstractmethod
class SwitchableDevice(ABC):
@abstractmethod
def turn_on(self):
pass
@abstractmethod
def turn_off(self):
pass
class LightBulb(SwitchableDevice):
def turn_on(self):
print("LightBulb ON")
def turn_off(self):
print("LightBulb OFF")
class LightSwitch:
def __init__(self, device: SwitchableDevice):
self.device = device
def operate(self, on):
if on:
self.device.turn_on()
else:
self.device.turn_off()
bulb = LightBulb()
switch = LightSwitch(bulb)
switch.operate(True) # Output: LightBulb ON
switch.operate(False) # Output: LightBulb OFF
Summary
S
Single Responsibility
A class should have only one reason to change—one responsibility
O
Open-Closed
A class should be open for extension, but closed for modification
L
Liskov Substitution
Subclasses should be substitutable for their base classes without altering correctness
I
Interface Segregation
Classes should not be forced to depend on methods they do not use
D
Dependency Inversion
High-level modules should not depend on low-level modules; both should depend on abstractions
Last updated