🏗️ Object-Oriented Programming

SOLID Principles

"SOLID is not a framework. It's a way of thinking about software design." — Robert C. Martin

S — Single Responsibility Principle (SRP)

A class should have only one reason to change.

Violation

class UserManager:
    def __init__(self, db):
        self.db = db

    def create_user(self, name: str, email: str) -> None:
        # Validate
        if "@" not in email:
            raise ValueError("Invalid email")

        # Save to DB
        self.db.save({"name": name, "email": email})

        # Send email
        smtp = SMTPClient("smtp.gmail.com")
        smtp.send(email, "Welcome!", f"Hi {name}...")

        # Log
        with open("audit.log", "a") as f:
            f.write(f"Created user {name}\n")
#include <string>
#include <fstream>
#include <iostream>

class UserManager {
    Database* db_;
public:
    explicit UserManager(Database* db) : db_(db) {}

    void createUser(const std::string& name, const std::string& email) {
        // Validate
        if (email.find('@') == std::string::npos)
            throw std::invalid_argument("Invalid email");

        // Save to DB
        db_->save({{"name", name}, {"email", email}});

        // Send email
        SMTPClient smtp("smtp.gmail.com");
        smtp.send(email, "Welcome!", "Hi " + name + "...");

        // Log
        std::ofstream f("audit.log", std::ios::app);
        f << "Created user " << name << "\n";
    }
};
import java.io.*;
import java.util.*;

public class UserManager {
    private final Database db;

    public UserManager(Database db) { this.db = db; }

    public void createUser(String name, String email) {
        // Validate
        if (!email.contains("@"))
            throw new IllegalArgumentException("Invalid email");

        // Save to DB
        db.save(Map.of("name", name, "email", email));

        // Send email
        SMTPClient smtp = new SMTPClient("smtp.gmail.com");
        smtp.send(email, "Welcome!", "Hi " + name + "...");

        // Log
        try (FileWriter f = new FileWriter("audit.log", true)) {
            f.write("Created user " + name + "\n");
        } catch (IOException e) { /* ignore */ }
    }
}
using System.IO;

public sealed class UserManager {
    private readonly Database _db;

    public UserManager(Database db) => _db = db;

    public void CreateUser(string name, string email) {
        // Validate
        if (!email.Contains("@"))
            throw new ArgumentException("Invalid email");

        // Save to DB
        _db.Save(new { name, email });

        // Send email
        var smtp = new SMTPClient("smtp.gmail.com");
        smtp.Send(email, "Welcome!", $"Hi {name}...");

        // Log
        File.AppendAllText("audit.log", $"Created user {name}\n");
    }
}

Refactored — Separated Concerns

class UserValidator:
    def validate(self, email: str) -> None:
        if "@" not in email:
            raise ValueError("Invalid email")

class UserRepository:
    def __init__(self, db):
        self.db = db
    def save(self, user: dict) -> None:
        self.db.save(user)

class EmailService:
    def send_welcome(self, email: str, name: str) -> None:
        smtp = SMTPClient("smtp.gmail.com")
        smtp.send(email, "Welcome!", f"Hi {name}...")

class AuditLogger:
    def log(self, msg: str) -> None:
        with open("audit.log", "a") as f:
            f.write(msg + "\n")

class UserManager:
    def __init__(self, validator: UserValidator, repo: UserRepository,
                 emailer: EmailService, logger: AuditLogger):
        self.validator = validator
        self.repo = repo
        self.emailer = emailer
        self.logger = logger

    def create_user(self, name: str, email: str) -> None:
        self.validator.validate(email)
        self.repo.save({"name": name, "email": email})
        self.emailer.send_welcome(email, name)
        self.logger.log(f"Created user {name}")
class UserValidator {
public:
    void validate(const std::string& email) {
        if (email.find('@') == std::string::npos)
            throw std::invalid_argument("Invalid email");
    }
};

class UserRepository {
    Database* db_;
public:
    explicit UserRepository(Database* db) : db_(db) {}
    void save(const User& u) { db_->save(u); }
};

class EmailService {
public:
    void sendWelcome(const std::string& email, const std::string& name) {
        SMTPClient smtp("smtp.gmail.com");
        smtp.send(email, "Welcome!", "Hi " + name + "...");
    }
};

class AuditLogger {
public:
    void log(const std::string& msg) {
        std::ofstream f("audit.log", std::ios::app);
        f << msg << "\n";
    }
};

class UserManager {
    UserValidator validator_;
    UserRepository repo_;
    EmailService emailer_;
    AuditLogger logger_;
public:
    UserManager(UserValidator v, UserRepository r, EmailService e, AuditLogger l)
        : validator_(v), repo_(r), emailer_(e), logger_(l) {}
    void createUser(const std::string& name, const std::string& email) {
        validator_.validate(email);
        repo_.save({{"name", name}, {"email", email}});
        emailer_.sendWelcome(email, name);
        logger_.log("Created user " + name);
    }
};
public final class UserValidator {
    public void validate(String email) {
        if (!email.contains("@")) throw new IllegalArgumentException("Invalid email");
    }
}

public final class UserRepository {
    private final Database db;
    public UserRepository(Database db) { this.db = db; }
    public void save(User u) { db.save(u); }
}

public final class EmailService {
    public void sendWelcome(String email, String name) {
        SMTPClient smtp = new SMTPClient("smtp.gmail.com");
        smtp.send(email, "Welcome!", "Hi " + name + "...");
    }
}

public final class AuditLogger {
    public void log(String msg) {
        try (FileWriter f = new FileWriter("audit.log", true)) {
            f.write(msg + "\n");
        } catch (IOException e) { /* ignore */ }
    }
}

public final class UserManager {
    private final UserValidator validator;
    private final UserRepository repo;
    private final EmailService emailer;
    private final AuditLogger logger;

    public UserManager(UserValidator v, UserRepository r, EmailService e, AuditLogger l) {
        validator = v; repo = r; emailer = e; logger = l;
    }
    public void createUser(String name, String email) {
        validator.validate(email);
        repo.save(Map.of("name", name, "email", email));
        emailer.sendWelcome(email, name);
        logger.log("Created user " + name);
    }
}
public sealed class UserValidator {
    public void Validate(string email) {
        if (!email.Contains("@")) throw new ArgumentException("Invalid email");
    }
}

public sealed class UserRepository {
    private readonly Database _db;
    public UserRepository(Database db) => _db = db;
    public void Save(User u) => _db.Save(u);
}

public sealed class EmailService {
    public void SendWelcome(string email, string name) {
        var smtp = new SMTPClient("smtp.gmail.com");
        smtp.Send(email, "Welcome!", $"Hi {name}...");
    }
}

public sealed class AuditLogger {
    public void Log(string msg) =>
        File.AppendAllText("audit.log", msg + "\n");
}

public sealed class UserManager {
    private readonly UserValidator _validator;
    private readonly UserRepository _repo;
    private readonly EmailService _emailer;
    private readonly AuditLogger _logger;

    public UserManager(UserValidator v, UserRepository r, EmailService e, AuditLogger l) {
        _validator = v; _repo = r; _emailer = e; _logger = l;
    }

    public void CreateUser(string name, string email) {
        _validator.Validate(email);
        _repo.Save(new { Name = name, Email = email });
        _emailer.SendWelcome(email, name);
        _logger.Log($"Created user {name}");
    }
}

O — Open/Closed Principle (OCP)

Software entities should be open for extension, closed for modification.

Violation

class PaymentProcessor:
    def process(self, payment_type: str, amount: float) -> bool:
        if payment_type == "credit_card":
            return self._process_credit_card(amount)
        elif payment_type == "paypal":
            return self._process_paypal(amount)
        elif payment_type == "crypto":
            return self._process_crypto(amount)
        else:
            raise ValueError("Unknown payment type")

    # Adding new payment type = MODIFY this class!
    def _process_credit_card(self, amount: float) -> bool: ...
    def _process_paypal(self, amount: float) -> bool: ...
    def _process_crypto(self, amount: float) -> bool: ...
class PaymentProcessor {
public:
    bool process(const std::string& type, double amount) {
        if (type == "credit_card") return processCreditCard(amount);
        else if (type == "paypal") return processPayPal(amount);
        else if (type == "crypto") return processCrypto(amount);
        else throw std::invalid_argument("Unknown payment type");
    }

private:
    bool processCreditCard(double amount) { /* ... */ }
    bool processPayPal(double amount) { /* ... */ }
    bool processCrypto(double amount) { /* ... */ }
    // Adding new payment type = MODIFY this class!
};
public class PaymentProcessor {
    public boolean process(String type, double amount) {
        if (type.equals("credit_card")) return processCreditCard(amount);
        else if (type.equals("paypal")) return processPayPal(amount);
        else if (type.equals("crypto")) return processCrypto(amount);
        else throw new IllegalArgumentException("Unknown payment type");
    }

    private boolean processCreditCard(double amount) { /* ... */ }
    private boolean processPayPal(double amount) { /* ... */ }
    private boolean processCrypto(double amount) { /* ... */ }
    // Adding new payment type = MODIFY this class!
}
public sealed class PaymentProcessor {
    public bool Process(string type, double amount) {
        return type switch {
            "credit_card" => ProcessCreditCard(amount),
            "paypal" => ProcessPayPal(amount),
            "crypto" => ProcessCrypto(amount),
            _ => throw new ArgumentException("Unknown payment type")
        };
    }

    private bool ProcessCreditCard(double amount) { /* ... */ }
    private bool ProcessPayPal(double amount) { /* ... */ }
    private bool ProcessCrypto(double amount) { /* ... */ }
    // Adding new payment type = MODIFY this class!
}

Refactored — Open for Extension

from abc import ABC, abstractmethod

class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount: float) -> bool:
        pass

class CreditCardPayment(PaymentMethod):
    def pay(self, amount: float) -> bool:
        print(f"Charging ${amount:.2f} to credit card")
        return True

class PayPalPayment(PaymentMethod):
    def pay(self, amount: float) -> bool:
        print(f"Charging ${amount:.2f} via PayPal")
        return True

class CryptoPayment(PaymentMethod):
    def pay(self, amount: float) -> bool:
        print(f"Charging ${amount:.2f} via Crypto")
        return True

# NEW payment = NEW class!
class ApplePayPayment(PaymentMethod):
    def pay(self, amount: float) -> bool:
        print(f"Charging ${amount:.2f} via Apple Pay")
        return True

class PaymentProcessor:
    def __init__(self):
        self._methods: Dict[str, PaymentMethod] = {}

    def register(self, type_: str, method: PaymentMethod) -> None:
        self._methods[type_] = method

    def process(self, type_: str, amount: float) -> bool:
        if type_ not in self._methods:
            raise ValueError("Unknown payment type")
        return self._methods[type_].pay(amount)
class PaymentMethod {
public:
    virtual ~PaymentMethod() = default;
    virtual bool pay(double amount) = 0;
};

class CreditCardPayment : public PaymentMethod {
    bool pay(double amount) override {
        std::cout << "Charging $" << amount << " to credit card\n";
        return true;
    }
};

class PayPalPayment : public PaymentMethod {
    bool pay(double amount) override {
        std::cout << "Charging $" << amount << " via PayPal\n";
        return true;
    }
};

class CryptoPayment : public PaymentMethod {
    bool pay(double amount) override {
        std::cout << "Charging $" << amount << " via Crypto\n";
        return true;
    }
};

// NEW payment = NEW class!
class ApplePayPayment : public PaymentMethod {
    bool pay(double amount) override {
        std::cout << "Charging $" << amount << " via Apple Pay\n";
        return true;
    }
};

class PaymentProcessor {
    std::unordered_map<std::string, std::unique_ptr<PaymentMethod>> methods_;
public:
    void registerMethod(std::string type, std::unique_ptr<PaymentMethod> method) {
        methods_[type] = std::move(method);
    }
    bool process(const std::string& type, double amount) {
        auto it = methods_.find(type);
        if (it == methods_.end()) throw std::runtime_error("Unknown payment type");
        return it->second->pay(amount);
    }
};
public interface PaymentMethod {
    boolean pay(double amount);
}

final class CreditCardPayment implements PaymentMethod {
    @Override public boolean pay(double amount) {
        System.out.println("Charging $" + amount + " to credit card");
        return true;
    }
}

final class PayPalPayment implements PaymentMethod {
    @Override public boolean pay(double amount) {
        System.out.println("Charging $" + amount + " via PayPal");
        return true;
    }
}

final class CryptoPayment implements PaymentMethod {
    @Override public boolean pay(double amount) {
        System.out.println("Charging $" + amount + " via Crypto");
        return true;
    }
}

// NEW payment = NEW class!
final class ApplePayPayment implements PaymentMethod {
    @Override public boolean pay(double amount) {
        System.out.println("Charging $" + amount + " via Apple Pay");
        return true;
    }
}

public final class PaymentProcessor {
    private final Map<String, PaymentMethod> methods = new HashMap<>();

    public void registerMethod(String type, PaymentMethod method) {
        methods.put(type, method);
    }

    public boolean process(String type, double amount) {
        PaymentMethod m = methods.get(type);
        if (m == null) throw new IllegalArgumentException("Unknown payment type");
        return m.pay(amount);
    }
}
public interface IPaymentMethod {
    bool Pay(double amount);
}

public sealed class CreditCardPayment : IPaymentMethod {
    public bool Pay(double amount) {
        Console.WriteLine($"Charging ${amount:F2} to credit card");
        return true;
    }
}

public sealed class PayPalPayment : IPaymentMethod {
    public bool Pay(double amount) {
        Console.WriteLine($"Charging ${amount:F2} via PayPal");
        return true;
    }
}

public sealed class CryptoPayment : IPaymentMethod {
    public bool Pay(double amount) {
        Console.WriteLine($"Charging ${amount:F2} via Crypto");
        return true;
    }
}

// NEW payment = NEW class!
public sealed class ApplePayPayment : IPaymentMethod {
    public bool Pay(double amount) {
        Console.WriteLine($"Charging ${amount:F2} via Apple Pay");
        return true;
    }
}

public sealed class PaymentProcessor {
    private readonly Dictionary<string, IPaymentMethod> _methods = new();

    public void Register(string type, IPaymentMethod method) {
        _methods[type] = method;
    }

    public bool Process(string type, double amount) {
        if (!_methods.TryGetValue(type, out var m))
            throw new ArgumentException("Unknown payment type");
        return m.Pay(amount);
    }
}

L — Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types.

Violation (Square/Rectangle)

class Rectangle:
    def __init__(self, width: int, height: int):
        self.width = width
        self.height = height

    def area(self) -> int:
        return self.width * self.height

class Square(Rectangle):
    def __init__(self, size: int):
        super().__init__(size, size)

    @property
    def width(self) -> int:
        return self._width

    @width.setter
    def width(self, value: int) -> None:
        self._width = self._height = value

    @property
    def height(self) -> int:
        return self._height

    @height.setter
    def height(self, value: int) -> None:
        self._width = self._height = value
class Rectangle {
protected:
    int width_, height_;
public:
    Rectangle(int w, int h) : width_(w), height_(h) {}
    virtual int width() const { return width_; }
    virtual int height() const { return height_; }
    virtual void width(int w) { width_ = w; }
    virtual void height(int h) { height_ = h; }
    int area() const { return width_ * height_; }
};

class Square : public Rectangle {
public:
    Square(int size) : Rectangle(size, size) {}
    void width(int w) override { width_ = height_ = w; }
    void height(int h) override { width_ = height_ = h; }
};
public class Rectangle {
    protected int width, height;
    public Rectangle(int w, int h) { width = w; height = h; }
    public int getWidth() { return width; }
    public int getHeight() { return height; }
    public void setWidth(int w) { width = w; }
    public void setHeight(int h) { height = h; }
    public int area() { return width * height; }
}

public class Square extends Rectangle {
    public Square(int size) { super(size, size); }
    @Override public void setWidth(int w) { width = height = w; }
    @Override public void setHeight(int h) { width = height = h; }
}
public class Rectangle {
    public virtual int Width { get; set; }
    public virtual int Height { get; set; }
    public Rectangle(int w, int h) { Width = w; Height = h; }
    public int Area() => Width * Height;
}

public class Square : Rectangle {
    public Square(int size) : base(size, size) {}
    public override int Width { set => Width = Height = value; }
    public override int Height { set => Width = Height = value; }
}
def resize(rect: Rectangle) -> None:
    rect.width = 10
    rect.height = 5
    assert rect.area() == 50  # FAILS for Square! (10×10=100)
void resize(Rectangle& rect) {
    rect.width(10);
    rect.height(5);
    assert(rect.area() == 50);  // FAILS for Square! (10×10=100)
}
void resize(Rectangle rect) {
    rect.setWidth(10);
    rect.setHeight(5);
    assert rect.area() == 50;  // FAILS for Square! (10×10=100)
}
void Resize(Rectangle rect) {
    rect.Width = 10;
    rect.Height = 5;
    Debug.Assert(rect.Area() == 50); // FAILS for Square! 10×10=100
}

Fix: Don't inherit Square from Rectangle. Use composition or make Square a factory function.


I — Interface Segregation Principle (ISP)

Many specific interfaces > one general interface

from abc import ABC, abstractmethod

# BAD — fat interface
class Worker(ABC):
    @abstractmethod
    def work(self) -> None: pass
    @abstractmethod
    def eat(self) -> None: pass
    @abstractmethod
    def sleep(self) -> None: pass

class Robot(Worker):
    def work(self) -> None: pass
    def eat(self) -> None: raise RuntimeError("Robots don't eat!")
    def sleep(self) -> None: raise RuntimeError("Robots don't sleep!")

# GOOD — segregated interfaces
class Workable(ABC):
    @abstractmethod
    def work(self) -> None: pass

class Eatable(ABC):
    @abstractmethod
    def eat(self) -> None: pass

class Sleepable(ABC):
    @abstractmethod
    def sleep(self) -> None: pass

class Human(Workable, Eatable, Sleepable):
    def work(self) -> None: pass
    def eat(self) -> None: pass
    def sleep(self) -> None: pass
// BAD — fat interface
class Worker {
public:
    virtual void work() = 0;
    virtual void eat() = 0;
    virtual void sleep() = 0;
    virtual ~Worker() = default;
};

class Robot : public Worker {
public:
    void work() override {}
    void eat() override { throw std::runtime_error("Robots don't eat!"); }
    void sleep() override { throw std::runtime_error("Robots don't sleep!"); }
};

// GOOD — segregated interfaces
class Workable { public: virtual void work() = 0; virtual ~Workable() = default; };
class Eatable  { public: virtual void eat() = 0;  virtual ~Eatable() = default; };
class Sleepable{ public: virtual void sleep() = 0; virtual ~Sleepable() = default; };

class Human : public Workable, public Eatable, public Sleepable {
public:
    void work() override {}
    void eat() override {}
    void sleep() override {}
};
// BAD — fat interface
interface Worker {
    void work();
    void eat();
    void sleep();
}

class Robot implements Worker {
    public void work() {}
    public void eat() { throw new RuntimeException("Robots don't eat!"); }
    public void sleep() { throw new RuntimeException("Robots don't sleep!"); }
}

// GOOD — segregated interfaces
interface Workable { void work(); }
interface Eatable { void eat(); }
interface Sleepable { void sleep(); }

class Human implements Workable, Eatable, Sleepable {
    public void work() {}
    public void eat() {}
    public void sleep() {}
}
// BAD — fat interface
interface IWorker {
    void Work();
    void Eat();
    void Sleep();
}

class Robot : IWorker {
    public void Work() {}
    public void Eat() => throw new Exception("Robots don't eat!");
    public void Sleep() => throw new Exception("Robots don't sleep!");
}

// GOOD — segregated interfaces
interface IWorkable { void Work(); }
interface IEatable { void Eat(); }
interface ISleepable { void Sleep(); }

public sealed class Human : IWorkable, IEatable, ISleepable {
    public void Work() {}
    public void Eat() {}
    public void Sleep() {}
}

Mixing Abstract + Concrete (Template Method Pattern)

from abc import ABC, abstractmethod
from typing import Callable, List

class DataExporter(ABC):
    @abstractmethod
    def write_header(self) -> None:
        pass

    @abstractmethod
    def write_row(self, data: dict) -> None:
        pass

    @abstractmethod
    def write_footer(self) -> None:
        pass

    # Concrete template method
    def export(self, data: List[dict]) -> None:
        self.write_header()
        for row in data:
            self.write_row(row)
        self.write_footer()

class CSVExporter(DataExporter):
    def write_header(self) -> None:
        print("id,name,email")

    def write_row(self, data: dict) -> None:
        print(f"{data['id']},{data['name']},{data['email']}")

    def write_footer(self) -> None:
        print("# End of CSV")

class JSONExporter(DataExporter):
    def write_header(self) -> None:
        print("[")

    def write_row(self, data: dict) -> None:
        print(f'  {{"id": {data["id"]}, "name": "{data["name"]}", "email": "{data["email"]}"}},')

    def write_footer(self) -> None:
        print("]")
class DataExporter {
public:
    virtual ~DataExporter() = default;
    virtual void writeHeader() = 0;
    virtual void writeRow(const std::string& data) = 0;
    virtual void writeFooter() = 0;

    // Concrete template method
    void export(const std::vector<std::string>& data) {
        writeHeader();
        for (const auto& row : data) writeRow(row);
        writeFooter();
    }
};

class CSVExporter : public DataExporter {
    void writeHeader() override { std::cout << "id,name,email\n"; }
    void writeRow(const std::string& data) override { std::cout << data << "\n"; }
    void writeFooter() override { std::cout << "# End of CSV\n"; }
};

class JSONExporter : public DataExporter {
    void writeHeader() override { std::cout << "[\n"; }
    void writeRow(const std::string& data) override { std::cout << "  " << data << ",\n"; }
    void writeFooter() override { std::cout << "]\n"; }
};
public abstract class DataExporter {
    public abstract void writeHeader();
    public abstract void writeRow(String data);
    public abstract void writeFooter();

    public void export(List<String> data) {
        writeHeader();
        for (String row : data) writeRow(row);
        writeFooter();
    }
}

class CSVExporter extends DataExporter {
    public void writeHeader() { System.out.println("id,name,email"); }
    public void writeRow(String data) { System.out.println(data); }
    public void writeFooter() { System.out.println("# End of CSV"); }
}

class JSONExporter extends DataExporter {
    public void writeHeader() { System.out.print("[\n"); }
    public void writeRow(String data) { System.out.println("  " + data + ","); }
    public void writeFooter() { System.out.println("]"); }
}
public abstract class DataExporter {
    public abstract void WriteHeader();
    public abstract void WriteRow(string data);
    public abstract void WriteFooter();

    public void Export(IEnumerable<string> data) {
        WriteHeader();
        foreach (var row in data) WriteRow(row);
        WriteFooter();
    }
}

public sealed class CSVExporter : DataExporter {
    public override void WriteHeader() => Console.WriteLine("id,name,email");
    public override void WriteRow(string data) => Console.WriteLine(data);
    public override void WriteFooter() => Console.WriteLine("# End of CSV");
}

public sealed class JSONExporter : DataExporter {
    public override void WriteHeader() => Console.WriteLine("[");
    public override void WriteRow(string data) => Console.WriteLine("  " + data + ",");
    public override void WriteFooter() => Console.WriteLine("]");
}