0
0
LldHow-ToBeginner · 4 min read

How to Apply SOLID Principles in Design for Better Software

To apply SOLID in design, follow its five principles: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. These guide you to write code that is easy to maintain, extend, and test by organizing responsibilities, using abstractions, and reducing dependencies.
📐

Syntax

The SOLID principles are a set of five design rules:

  • Single Responsibility Principle (SRP): A class should have only one reason to change.
  • Open/Closed Principle (OCP): Software entities should be open for extension but closed for modification.
  • Liskov Substitution Principle (LSP): Subtypes must be substitutable for their base types.
  • Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use.
  • Dependency Inversion Principle (DIP): Depend on abstractions, not on concrete implementations.
python
class SingleResponsibility:
    def __init__(self, data):
        self.data = data

    def process(self):
        # Only one responsibility: process data
        return self.data.upper()

class OpenClosed:
    def operation(self):
        pass

class Extension(OpenClosed):
    def operation(self):
        return "Extended behavior"

class Base:
    def action(self):
        return "Base action"

class Derived(Base):
    def action(self):
        return "Derived action"

class ISP:
    def method1(self):
        pass

    def method2(self):
        pass

class Client(ISP):
    def method1(self):
        return "Used method1"

    def method2(self):
        raise NotImplementedError("Not used")

class DIP:
    def __init__(self, abstraction):
        self.abstraction = abstraction

    def execute(self):
        return self.abstraction.do()

class Abstraction:
    def do(self):
        return "Doing something"
💻

Example

This example shows how to apply SOLID principles in a simple payment system design. Each class has a clear responsibility, uses interfaces, and depends on abstractions.

python
from abc import ABC, abstractmethod

# Single Responsibility: Each class has one job
class PaymentProcessor(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

class CreditCardProcessor(PaymentProcessor):
    def pay(self, amount):
        return f"Paid {amount} using Credit Card"

class PaypalProcessor(PaymentProcessor):
    def pay(self, amount):
        return f"Paid {amount} using PayPal"

# Dependency Inversion: High-level module depends on abstraction
class Checkout:
    def __init__(self, processor: PaymentProcessor):
        self.processor = processor

    def checkout(self, amount):
        return self.processor.pay(amount)

# Usage
credit_card = CreditCardProcessor()
paypal = PaypalProcessor()

checkout1 = Checkout(credit_card)
checkout2 = Checkout(paypal)

print(checkout1.checkout(100))
print(checkout2.checkout(200))
Output
Paid 100 using Credit Card Paid 200 using PayPal
⚠️

Common Pitfalls

Common mistakes when applying SOLID include:

  • Violating SRP by giving a class multiple responsibilities, making it hard to maintain.
  • Breaking OCP by modifying existing code instead of extending it, causing bugs.
  • Ignoring LSP by creating subclasses that do not behave like their parents, leading to unexpected errors.
  • Forcing clients to depend on unused methods, violating ISP and increasing coupling.
  • Depending on concrete classes instead of abstractions, making code rigid and hard to test (breaking DIP).
python
class BadProcessor:
    def pay(self, amount):
        print(f"Paying {amount}")
    def refund(self, amount):
        print(f"Refunding {amount}")

# Client forced to implement refund even if not needed
class Client:
    def __init__(self, processor):
        self.processor = processor

    def pay(self, amount):
        self.processor.pay(amount)

    def refund(self, amount):
        # Not all clients need refund, but forced to implement
        self.processor.refund(amount)

# Better approach: split interfaces
class PaymentProcessor:
    def pay(self, amount):
        pass

class RefundProcessor:
    def refund(self, amount):
        pass
📊

Quick Reference

Remember these quick tips to apply SOLID:

  • SRP: One class, one job.
  • OCP: Add new code, don’t change old code.
  • LSP: Subclasses behave like parents.
  • ISP: Use small, specific interfaces.
  • DIP: Depend on interfaces, not implementations.

Key Takeaways

Apply SOLID principles to write clean, maintainable, and scalable code.
Keep classes focused on a single responsibility to reduce complexity.
Use abstractions and interfaces to make your design flexible and testable.
Avoid forcing clients to depend on unused methods by splitting interfaces.
Extend behavior by adding new code instead of modifying existing code.