Last updated: 2026-09-23

U
Undergraduate level

SOLID Principles

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

The five principles below aren't a single, unified invention. Martin formulated and named the Single Responsibility, Interface Segregation, and Dependency Inversion principles in a 2000 essay collecting design guidance he'd been teaching for years1; the neat acronym "SOLID" itself was coined a few years later by Michael Feathers, who noticed the five principles' initials happened to spell a word. Two of the five actually predate Martin's essay: the Open/Closed Principle originates with Bertrand Meyer's 1988 Object-Oriented Software Construction, in almost the exact form used below2, and the Liskov Substitution Principle is a direct application of Liskov and Wing's formal subtyping work, also covered on this site's Inheritance & Composition page. SOLID is a curated, well-named collection, not a single coherent theory with one author — which is worth knowing before treating it as more unified than it actually is.

Note well. SOLID is a curated, well-named collection, not a single coherent theory with one author — two of the five principles predate Martin's essay and the acronym itself was coined later by Michael Feathers.

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");
    }
}
class UserManager
  def initialize(db)
    @db = db
  end

  def create_user(name, email)
    # Validate
    raise ArgumentError, "Invalid email" unless email.include?("@")

    # Save to DB
    @db.save(name: name, email: email)

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

    # Log
    File.open("audit.log", "a") { |f| f.puts "Created user #{name}" }
  end
end

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}");
    }
}
class UserValidator
  def validate(email)
    raise ArgumentError, "Invalid email" unless email.include?("@")
  end
end

class UserRepository
  def initialize(db)
    @db = db
  end

  def save(user)
    @db.save(user)
  end
end

class EmailService
  def send_welcome(email, name)
    smtp = SMTPClient.new("smtp.gmail.com")
    smtp.send(email, "Welcome!", "Hi #{name}...")
  end
end

class AuditLogger
  def log(msg)
    File.open("audit.log", "a") { |f| f.puts msg }
  end
end

class UserManager
  def initialize(validator, repo, emailer, logger)
    @validator = validator
    @repo = repo
    @emailer = emailer
    @logger = logger
  end

  def create_user(name, email)
    @validator.validate(email)
    @repo.save(name: name, email: email)
    @emailer.send_welcome(email, name)
    @logger.log("Created user #{name}")
  end
end

O — Open/Closed Principle (OCP)

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

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!
}
class PaymentProcessor
  def process(payment_type, amount)
    case payment_type
    when "credit_card" then process_credit_card(amount)
    when "paypal"      then process_paypal(amount)
    when "crypto"      then process_crypto(amount)
    else raise ArgumentError, "Unknown payment type"
    end
  end

  private

  # Adding a new payment type = MODIFY this class!
  def process_credit_card(amount); end
  def process_paypal(amount); end
  def process_crypto(amount); end
end

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);
    }
}
class CreditCardPayment
  def pay(amount)
    puts format("Charging $%.2f to credit card", amount)
    true
  end
end

class PayPalPayment
  def pay(amount)
    puts format("Charging $%.2f via PayPal", amount)
    true
  end
end

class CryptoPayment
  def pay(amount)
    puts format("Charging $%.2f via Crypto", amount)
    true
  end
end

# NEW payment = NEW class!
class ApplePayPayment
  def pay(amount)
    puts format("Charging $%.2f via Apple Pay", amount)
    true
  end
end

class PaymentProcessor
  def initialize
    @methods = {}
  end

  def register(type, method)
    @methods[type] = method
  end

  def process(type, amount)
    raise ArgumentError, "Unknown payment type" unless @methods.key?(type)

    @methods[type].pay(amount)
  end
end

L — Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types.3

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; }
}
class Rectangle
  attr_accessor :width, :height

  def initialize(width, height)
    @width = width
    @height = height
  end

  def area
    width * height
  end
end

class Square < Rectangle
  def initialize(size)
    super(size, size)
  end

  def width=(value)
    @width = @height = value
  end

  def height=(value)
    @width = @height = value
  end
end
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
}
def resize(rect)
  rect.width = 10
  rect.height = 5
  raise "LSP violated!" unless rect.area == 50 # FAILS for Square! (10×10=100)
end

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

Don't do this! Don't inherit Square from Rectangle — a setter that forces both dimensions together breaks any code that assumes independent width and height. Use composition or a factory function instead.

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() {}
}
# BAD — fat interface (a module used as one)
module Worker
  def work = raise NotImplementedError
  def eat = raise NotImplementedError
  def sleep = raise NotImplementedError
end

class Robot
  include Worker
  def work; end
  def eat = raise "Robots don't eat!"
  def sleep = raise "Robots don't sleep!"
end

# GOOD — segregated modules, mixed in as needed
module Workable
  def work = raise NotImplementedError
end

module Eatable
  def eat = raise NotImplementedError
end

module Sleepable
  def sleep = raise NotImplementedError
end

class Human
  include Workable
  include Eatable
  include Sleepable

  def work; end
  def eat; end
  def sleep; end
end

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("]");
}
class DataExporter
  # Concrete template method
  def export(data)
    write_header
    data.each { |row| write_row(row) }
    write_footer
  end

  def write_header = raise NotImplementedError
  def write_row(data) = raise NotImplementedError
  def write_footer = raise NotImplementedError
end

class CSVExporter < DataExporter
  def write_header
    puts "id,name,email"
  end

  def write_row(data)
    puts "#{data[:id]},#{data[:name]},#{data[:email]}"
  end

  def write_footer
    puts "# End of CSV"
  end
end

class JSONExporter < DataExporter
  def write_header
    puts "["
  end

  def write_row(data)
    puts %(  {"id": #{data[:id]}, "name": "#{data[:name]}", "email": "#{data[:email]}"},)
  end

  def write_footer
    puts "]"
  end
end
  • Inheritance & Composition — a full worked example of the Liskov Substitution Principle covered here, including the classic Square/Rectangle violation.
  • Data Structures as Behavioural Contracts — the standard library's own masterclass in the dependency-inversion habit named here.
  • Polymorphism & Interfaces — its own worked example of the Interface Segregation Principle, with a fat "Worker" interface split into Workable/Eatable/Sleepable.
  • Design Patterns — many of the Gang of Four's patterns exist specifically to satisfy one or more of these five principles.

References


  1. Martin, R. C. (2000). Design Principles and Design Patterns. objectmentor.com. The acronym "SOLID" was coined later by Michael Feathers.

  2. Meyer, B. (1988). Object-Oriented Software Construction. Prentice Hall. Source of the Open/Closed Principle. Held by the University of Reading Library (a later edition).

  3. Liskov, B., & Wing, J. (1994). A behavioral notion of subtyping. ACM Transactions on Programming Languages and Systems, 16(6), 1811–1841. https://doi.org/10.1145/197320.197383