Mastering SOLID Principles for Clean Architecture

“Breaking Down the Five Core SOLID Principles…” and ending with: “…without modifying a single line of the core order-processing business logic.”

with the following structured content. You can copy and paste the formatted blocks directly into your WordPress editor (using standard HeadingParagraph, and Code blocks).


1. Single Responsibility Principle (SRP)

“A class should have one, and only one, reason to change.”

A class should do one thing and do it well. If a class has multiple responsibilities, changes to one responsibility could inadvertently break or degrade other functionalities.

The Problem (Violation)

An Invoice class that calculates totals and saves the invoice to a database and sends email notifications.

The Solution (C++ & Python)

C++ Example

#include <iostream>
#include <string>

// Responsibility 1: Manage Invoice Data
class Invoice {
public:
    double amount;
    Invoice(double amt) : amount(amt) {}
};

// Responsibility 2: Handle Database Persistence
class InvoiceRepository {
public:
    void saveToDatabase(const Invoice& invoice) {
        std::cout << "Saving invoice of $" << invoice.amount << " to database.\n";
    }
};

// Responsibility 3: Handle Notifications
class EmailService {
public:
    void sendInvoiceEmail(const Invoice& invoice) {
        std::cout << "Emailing invoice receipt of $" << invoice.amount << ".\n";
    }
};

Python Example

# Responsibility 1: Manage Invoice Data
class Invoice:
    def __init__(self, amount: float):
        self.amount = amount

# Responsibility 2: Handle Database Persistence
class InvoiceRepository:
    def save_to_database(self, invoice: Invoice):
        print(f"Saving invoice of ${invoice.amount} to database.")

# Responsibility 3: Handle Notifications
class EmailService:
    def send_invoice_email(self, invoice: Invoice):
        print(f"Emailing invoice receipt of ${invoice.amount}.")

2. Open/Closed Principle (OCP)

“Software entities should be open for extension, but closed for modification.”

You should be able to add new functionality to a system without modifying existing, tested code. This is typically achieved using interfaces or abstract base classes.

The Problem (Violation)

Using if/else or switch statements inside a billing class to handle different payment types (e.g., Credit Card, PayPal). Adding a new payment type forces you to edit the existing code, risking regressions.

The Solution (C++ & Python)

C++ Example

#include <iostream>
#include <memory>
#include <vector>

// Interface: Open for extension
class PaymentMethod {
public:
    virtual ~PaymentMethod() = default;
    virtual void processPayment(double amount) = 0;
};

// Extension 1: Credit Card
class CreditCardPayment : public PaymentMethod {
public:
    void processPayment(double amount) override {
        std::cout << "Processing $" << amount << " via Credit Card.\n";
    }
};

// Extension 2: PayPal
class PayPalPayment : public PaymentMethod {
public:
    void processPayment(double amount) override {
        std::cout << "Processing $" << amount << " via PayPal.\n";
    }
};

// Closed for modification: This processor never needs to change when adding new payment methods
class PaymentProcessor {
public:
    void checkout(PaymentMethod& method, double amount) {
        method.processPayment(amount);
    }
};

Python Example

from abc import ABC, abstractmethod

# Interface: Open for extension
class PaymentMethod(ABC):
    @abstractmethod
    def process_payment(self, amount: float):
        pass

# Extension 1: Credit Card
class CreditCardPayment(PaymentMethod):
    def process_payment(self, amount: float):
        print(f"Processing ${amount} via Credit Card.")

# Extension 2: PayPal
class PayPalPayment(PaymentMethod):
    def process_payment(self, amount: float):
        print(f"Processing ${amount} via PayPal.")

# Closed for modification
class PaymentProcessor:
    def checkout(self, payment_method: PaymentMethod, amount: float):
        payment_method.process_payment(amount)

3. Liskov Substitution Principle (LSP)

“Subtypes must be substitutable for their base types.”

If class B is a subclass of class A, we should be able to pass an object of class B to any method that expects class A without breaking the application’s correctness.

The Problem (Violation)

An Ostrich class inherits from a Bird class that has a fly() method. Since an ostrich cannot fly, calling fly() on an ostrich object will throw an runtime exception or cause unexpected behavior.

The Solution (C++ & Python)

C++ Example

#include <iostream>

// Base Class: Generic Bird
class Bird {
public:
    virtual ~Bird() = default;
    virtual void eat() {
        std::cout << "This bird is eating.\n";
    }
};

// Sub-interface for Flying Birds
class FlyingBird : public Bird {
public:
    virtual void fly() = 0;
};

class Sparrow : public FlyingBird {
public:
    void fly() override {
        std::cout << "Sparrow is flying high!\n";
    }
};

class Ostrich : public Bird {
    // Ostriches do not inherit fly(), preventing unexpected errors
};

Python Example

class Bird:
    def eat(self):
        print("This bird is eating.")

class FlyingBird(Bird):
    def fly(self):
        print("This bird is flying!")

class Sparrow(FlyingBird):
    pass

class Ostrich(Bird):
    # Ostrich does not inherit fly() because it cannot fly
    pass

4. Interface Segregation Principle (ISP)

“Clients should not be forced to depend on interfaces they do not use.”

It is better to have several small, specific interfaces rather than one large, general-purpose interface.

The Problem (Violation)

An interface called IMachine has methods print()scan(), and fax(). A simple BasicPrinter that can only print is forced to implement dummy or exception-throwing methods for scan() and fax().

The Solution (C++ & Python)

C++ Example

#include <iostream>

// Segmented, focused interfaces
class Printer {
public:
    virtual ~Printer() = default;
    virtual void print() = 0;
};

class Scanner {
public:
    virtual ~Scanner() = default;
    virtual void scan() = 0;
};

// Simple Printer implements only what it needs
class BasicPrinter : public Printer {
public:
    void print() override {
        std::cout << "Printing document...\n";
    }
};

// Advanced Multi-function device implements both interfaces
class MultiFunctionPrinter : public Printer, public Scanner {
public:
    void print() override { std::cout << "Printing document...\n"; }
    void scan() override { std::cout << "Scanning document...\n"; }
};

Python Example

from abc import ABC, abstractmethod

# Segmented, focused interfaces
class Printer(ABC):
    @abstractmethod
    def print_document(self):
        pass

class Scanner(ABC):
    @abstractmethod
    def scan_document(self):
        pass

# Simple Printer only implements printing capabilities
class BasicPrinter(Printer):
    def print_document(self):
        print("Printing document...")

# Multi-function printer implements both
class SmartPrinter(Printer, Scanner):
    def print_document(self):
        print("Printing document...")
        
    def scan_document(self):
        print("Scanning document...")

5. Dependency Inversion Principle (DIP)

“High-level modules should not depend on low-level modules. Both should depend on abstractions.”

Decouple your system components by making them depend on interfaces/abstract classes rather than concrete class implementations.

The Problem (Violation)

A high-level Car class directly instantiates and depends on a concrete V8Engine class. If you want to change the engine type to an ElectricEngine, you have to modify the Car class.

The Solution (C++ & Python)

C++ Example

#include <iostream>
#include <memory>

// Abstraction (Interface)
class Engine {
public:
    virtual ~Engine() = default;
    virtual void start() = 0;
};

// Concrete implementation 1
class V8Engine : public Engine {
public:
    void start() override {
        std::cout << "V8 Engine roars to life!\n";
    }
};

// Concrete implementation 2
class ElectricEngine : public Engine {
public:
    void start() override {
        std::cout << "Electric motor hums quietly.\n";
    }
};

// High-level class depends on the abstraction (Engine)
class Car {
private:
    std::shared_ptr<Engine> engine;
public:
    Car(std::shared_ptr<Engine> eng) : engine(eng) {}
    
    void drive() {
        engine->start();
        std::cout << "Car is moving.\n";
    }
};

Python Example

from abc import ABC, abstractmethod

# Abstraction (Interface)
class Engine(ABC):
    @abstractmethod
    def start(self):
        pass

# Concrete implementations
class V8Engine(Engine):
    def start(self):
        print("V8 Engine roars to life!")

class ElectricEngine(Engine):
    def start(self):
        print("Electric motor hums quietly.")

# High-level class depends on the abstraction (Engine)
class Car:
    def __init__(self, engine: Engine):
        self.engine = engine  # Dependency Injection

    def drive(self):
        self.engine.start()
        print("Car is moving.")

Follow our YouTube Channels for Coding Concepts & Architecture:

Join Our Technical Communities for Daily Study Guides & Alerts:

Deep-Dive DSA & Coding Reference Guide:


Mastering the SOLID principles is a transformative milestone in any software engineer’s career, turning complex coding challenges into structured, elegant solutions. By consistently applying these five design patterns, you ensure that your software architectures remain clean, adaptable, and prepared for future enterprise demands. Continue leveraging high-quality educational resources, engaging with technical communities, and building projects that showcase your clean architecture expertise to stand out in the competitive tech industry.

Mastering SOLID Principles for Clean Architecture: The Ultimate Guide for Backend Engineers

⚠️ SYSTEM METADATA SECTION (Hidden from website view, visible to SEO engines):
Keywords: SOLID Principles, Clean Architecture, System Design, Backend Engineering
Tags: SOLID Principles, Clean Architecture, Software Engineering, Object-Oriented Programming, System Design, Single Responsibility Principle, Open Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, Dependency Inversion Principle, Backend Development, Software Design Patterns, VS Coding Academy, Trendy VS Vlogs, Hire Alert Jobs, Enterprise Architecture, Code Refactoring, Decoupled Systems, Unit Testing, Technical Interview Preparation, Software Scalability, Clean Code, Software Architecture, Design Principles, OOP, Maintainable Code, Dependency Injection, Software Developer Career, System Design Roadmap, Coding Best Practices, Microservices Architecture, API Design, Java Design Patterns, C# SOLID, Python Clean Architecture, High-Level Design, Low-Level Design, Software Engineering Interview, Mocking Dependencies, Separation of Concerns, Loose Coupling, High Cohesion, Agile Software Development, Code Quality, Technical Debt, Domain Driven Design, Test Driven Development, Backend Architecture, Senior Software Engineer, Programming Patterns

Leave a Comment