← Back to Python

All Topics

Advertisement

Learn/Python/Advanced Python

SOLID Principles - Single Responsibility, Open-Closed, etc

Topic: Software Design

Advertisement

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

  1. Refactor a class with multiple responsibilities into focused classes.
  2. Implement the open-closed principle using a plugin system.
  3. Create proper abstractions following the Liskov Substitution Principle.
  4. Design interfaces that follow the Interface Segregation Principle.
  5. Implement dependency injection for a service layer.

Advertisement

Advertisement

Need More Practice?

Get personalized Python help from ChatWhole's AI-powered platform.

Get Expert Help →