Introduction
SOLID is an acronym for five design principles that make software designs more understandable, flexible, and maintainable. These principles were introduced by Robert C. Martin and form the foundation of object-oriented programming.
Single Responsibility Principle (S)
A class should have only one reason to change, meaning it should have only one job or responsibility.
class User:
def __init__(self, name, email):
self.name = name
self.email = email
class UserValidator:
@staticmethod
def validate(user):
if "@" not in user.email:
raise ValueError("Invalid email")
return True
class UserRepository:
def save(self, user):
print(f"Saving user {user.name} to database")
class UserNotifier:
@staticmethod
def send_welcome_email(user):
print(f"Sending welcome email to {user.email}")
user = User("John", "john@example.com")
UserValidator.validate(user)
UserRepository().save(user)
UserNotifier.send_welcome_email(user)
Open-Closed Principle (O)
Software entities should be open for extension but closed for modification.
from abc import ABC, abstractmethod
class Discount(ABC):
@abstractmethod
def calculate(self, price):
pass
class NoDiscount(Discount):
def calculate(self, price):
return price
class PercentageDiscount(Discount):
def __init__(self, percentage):
self.percentage = percentage
def calculate(self, price):
return price * (1 - self.percentage / 100)
class FixedDiscount(Discount):
def __init__(self, amount):
self.amount = amount
def calculate(self, price):
return max(price - self.amount, 0)
class PriceCalculator:
def __init__(self, discount):
self.discount = discount
def calculate_price(self, price):
return self.discount.calculate(price)
calculator = PriceCalculator(PercentageDiscount(10))
print(calculator.calculate_price(100)) # 90
Liskov Substitution Principle (L)
Objects of a superclass should be replaceable with objects of a subclass without affecting correctness.
class Bird:
def fly(self):
return "Flying"
class Sparrow(Bird):
def fly(self):
return "Sparrow flying"
class Penguin(Bird):
def fly(self):
raise NotImplementedError("Penguins cannot fly")
def make_bird_fly(bird):
return bird.fly()
sparrow = Sparrow()
print(make_bird_fly(sparrow))
Interface Segregation Principle (I)
Clients should not be forced to depend on interfaces they do not use.
class Printer:
def print(self):
pass
class Scanner:
def scan(self):
pass
class Fax:
def fax(self):
pass
class MultiFunctionPrinter(Printer, Scanner, Fax):
def print(self):
print("Printing")
def scan(self):
print("Scanning")
def fax(self):
print("Faxing")
class SimplePrinter(Printer):
def print(self):
print("Printing only")
Dependency Inversion Principle (D)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
from abc import ABC
class Storage(ABC):
def save(self, data):
pass
class DatabaseStorage(Storage):
def save(self, data):
print(f"Saving {data} to database")
class FileStorage(Storage):
def save(self, data):
print(f"Saving {data} to file")
class DataManager:
def __init__(self, storage):
self.storage = storage
def save_data(self, data):
self.storage.save(data)
manager = DataManager(DatabaseStorage())
manager.save_data("important data")
Practice Problems
- Refactor a class with multiple responsibilities into focused classes.
- Implement the open-closed principle using a plugin system.
- Create proper abstractions following the Liskov Substitution Principle.
- Design interfaces that follow the Interface Segregation Principle.
- Implement dependency injection for a service layer.