SOLID Principles

1. Single Responsibility Principle (SRP)

Definition: A class should have only one reason to change (one responsibility).

Bad Example:

Copyclass Report {
public:
    void generateReport() { /* logic to generate */ }
    void saveToFile() { /* logic to save to file */ }
    void printReport() { /* logic to print */ }
};
  • This class handles too many responsibilities (generate, save, print).

Good Example (SRP):

Copyclass Report {
public:
    void generateReport() { /* logic */ }
};

class FileManager {
public:
    void saveToFile(const Report& report) { /* logic */ }
};

class Printer {
public:
    void printReport(const Report& report) { /* logic */ }
};
  • Each class has only one reason to change.

2. Open/Closed Principle (OCP)

Definition: A class should be open for extension, but closed for modification.

Bad Example:

Copyclass Shape {
public:
    string type;
};

class AreaCalculator {
public:
    double calculate(Shape* shape) {
        if (shape->type == "circle") {
            return 3.14 * 5 * 5;
        } else if (shape->type == "rectangle") {
            return 10 * 5;
        }
        return 0;
    }
};
  • Every time we add a new shape, we must modify AreaCalculator.

Good Example (OCP):

Copyclass Shape {
public:
    virtual double area() const = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape {
    double r;
public:
    Circle(double r) : r(r) {}
    double area() const override { return 3.14 * r * r; }
};

class Rectangle : public Shape {
    double w, h;
public:
    Rectangle(double w, double h) : w(w), h(h) {}
    double area() const override { return w * h; }
};
  • Now adding a new shape doesn’t require modifying existing code — just extend by creating a new class.

3. Liskov Substitution Principle (LSP)

Definition: Subclasses should be usable in place of base classes without breaking functionality.

Bad Example:

Copyclass Bird {
public:
    virtual void fly() { cout << "Bird flying"; }
};

class Ostrich : public Bird {
public:
    void fly() override { throw runtime_error("Ostrich can't fly"); }
};
  • Ostrich violates LSP because a client expecting Bird cannot safely call fly().

Good Example (LSP):

Copyclass Bird { };

class FlyingBird : public Bird {
public:
    virtual void fly() = 0;
};

class Sparrow : public FlyingBird {
public:
    void fly() override { cout << "Sparrow flying"; }
};

class Ostrich : public Bird {
    // Ostrich is a Bird but not a FlyingBird
};
  • Correct hierarchy: non-flying birds don’t override fly.

4. Interface Segregation Principle (ISP)

Definition: Clients should not be forced to implement methods they don’t use.

Bad Example:

Copyclass IMachine {
public:
    virtual void print() = 0;
    virtual void scan() = 0;
    virtual void fax() = 0;
};
class BasicPrinter : public IMachine {
public:
    void print() override { cout << "Printing"; }
    void scan() override { /* Not supported */ }
    void fax() override { /* Not supported */ }
};
  • BasicPrinter is forced to implement methods it doesn’t need.

Good Example (ISP):

Copyclass IPrinter {
public:
    virtual void print() = 0;
};

class IScanner {
public:
    virtual void scan() = 0;
};

class BasicPrinter : public IPrinter {
public:
    void print() override { cout << "Printing"; }
};
  • Separate small interfaces → clients implement only what they need.

5. Dependency Inversion Principle (DIP)

Definition: High-level modules should depend on abstractions, not concrete classes.

Bad Example:

Copyclass LightBulb {
public:
    void turnOn() { cout << "Bulb on"; }
    void turnOff() { cout << "Bulb off"; }
};

class Switch {
    LightBulb bulb; // depends on concrete class
public:
    void operate(bool on) {
        if (on) bulb.turnOn(); else bulb.turnOff();
    }
};
  • Switch is tightly coupled to LightBulb.

Good Example (DIP):

Copyclass ISwitchable {
public:
    virtual void turnOn() = 0;
    virtual void turnOff() = 0;
    virtual ~ISwitchable() = default;
};

class LightBulb : public ISwitchable {
public:
    void turnOn() override { cout << "Bulb on"; }
    void turnOff() override { cout << "Bulb off"; }
};

class Switch {
    ISwitchable* device;
public:
    Switch(ISwitchable* d) : device(d) {}
    void operate(bool on) {
        if (on) device->turnOn(); else device->turnOff();
    }
};
  • Switch depends only on abstraction, not implementation.
Updated on