Last updated: 2026-09-23
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.
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.
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
Related Topics
- 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
Martin, R. C. (2000). Design Principles and Design Patterns. objectmentor.com. The acronym "SOLID" was coined later by Michael Feathers. ↩
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). ↩↩
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 ↩