SOLID Design Principle

SOLID Design Principle

Quick reference for the SOLID principles, five foundation stones of maintainable, extensible object-oriented design:

1. Single Responsibility Principle (SRP)

  • What: A class (or module) should have only one reason to change, i.e. one job.
  • Why: Keeps your code easy to understand, test, and modify, because each unit is narrowly focused.
  • Example:

# ❌ Bad: this class both reads config and writes logs

class AppManager:

    def load_config(...): …

    def write_log(...): …

# ✅ Good: split into two focused classes

class ConfigLoader:

    def load(...): …

class Logger:

    def log(...): …        

2. Open/Closed Principle (OCP)

  • What: “Software entities (classes, functions, modules) should be open for extension, but closed for modification.”
  • Why: You can add new behaviour without touching existing, tested code, minimising risk.
  • Example:

# ❌ Bad: adding a new shape requires modifying switch
def area(shape):
    if shape.type == 'circle': …
    elif shape.type == 'square': …

# ✅ Good: use polymorphism
class Shape:
    def area(self): pass
class Circle(Shape):
    def area(self): …
class Square(Shape):
    def area(self): …

def total_area(shapes: List[Shape]):
    return sum(s.area() for s in shapes)        

3. Liskov Substitution Principle (LSP)

  • What: Subtypes must be substitutable for their base types without breaking correctness.
  • Why: Ensures your clients can use any subclass transparently, so that you can swap implementations safely.
  • Example:

class Bird:
    def fly(self): …

class Sparrow(Bird): …
class Penguin(Bird):
    def fly(self):
        raise Exception("I can't!")   # ❌ Violates LSP

# If you need non-flying birds,
#    introduce a separate interface (e.g. Swimmable).        

4. Interface Segregation Principle (ISP)

  • What: Clients should not be forced to depend on methods they don’t use, i.e. fine-grained interfaces.
  • Why: Keeps implementing classes lean and focused; avoids “fat” interfaces that break SRP.
  • Example:

# ❌ Fat interface
class Worker:
    def code(): …
    def test(): …
    def deploy(): …

# ✅ Segregated
class Coder:
    def code(): …
class Tester:
    def test(): …
class Deployer:
    def deploy(): …        

5. Dependency Inversion Principle (DIP)

  • What: High-level modules should not depend on low-level modules; both should depend on abstractions.
  • Why: Decouples your system, making it easier to swap concrete implementations (e.g. for testing or new strategies).
  • Example:

# ❌ High-level code instantiates low-level class directly
class UserService:
    def __init__(self):
        self.repo = SqlUserRepository()

# ✅ Depend on an abstraction
class UserRepository(Protocol):
    def get_user(id): …

class SqlUserRepository(UserRepository): …
class InMemoryUserRepository(UserRepository): …

class UserService:
    def __init__(self, repo: UserRepository):
        self.repo = repo        

By applying SRP, OCP, LSP, ISP, and DIP, your code remains modular, testable, and resilient to change, allowing you to evolve features without fear of unintended side effects.

To view or add a comment, sign in

More articles by Raj Kishore Singh

Explore content categories