"SOLID is not a framework. It's a way of thinking about software design." — Robert C. Martin
A class should have only one reason to change.
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 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}");
}
}
Software entities should be open for extension, closed for modification.
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!
}
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);
}
}
Subtypes must be substitutable for their base types.
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.
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() {}
}
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("]");
}