Single Responsibility Principle (SRP)
A module should have one, and only one, reason to change.
Better phrasing: A module should be responsible to one, and only one, actor.
An actor is a user, stakeholder, or stakeholder group with distinct business interests.
Symptoms of Violation
Accidental Duplication
Example: Employee class with:
calculatePay()— used by CFOreportHours()— used by COOsave()— used by CTO
Shared helper regularHours() changed for CFO breaks COO reports silently.
Merges
Frequent merge conflicts when different actors’ teams edit the same file.
Open-Closed Principle (OCP)
A software artifact should be open for extension but closed for modification.
Small requirement changes should not force large modifications.
Goal: protect high-level policies (business logic) from low-level changes (UI, database, frameworks).
Interactor (core business logic) should remain untouched by changes in database, controller, presenter, or view.
OCP Example – Payment Processor
abstract class PaymentProcessor {
abstract process(amount: number): void;
}
class CreditCardPaymentProcessor extends PaymentProcessor {
process(amount: number): void {
console.log(`Processing payment of ${amount} via credit card`);
}
}
class PaypalPaymentProcessor extends PaymentProcessor {
process(amount: number): void {
console.log(`Processing payment of ${amount} via PayPal`);
}
}
const creditCard = new CreditCardPaymentProcessor();
const paypal = new PaypalPaymentProcessor();
creditCard.process(100);
paypal.process(200);
New payment methods added via new subclasses — no changes to existing code.
Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types without breaking program correctness.
Derived class must preserve behavioral expectations (preconditions, postconditions, invariants) of the base class.
Interface Segregation Principle (ISP)
Clients should not be forced to depend on methods they do not use.
Large (“fat”) interfaces should be split into smaller, client-specific ones.
ISP Example
Bad:
interface OrderService {
orderBurger(quantity: number): void;
orderFries(quantity: number): void;
orderCombo(burgers: number, fries: number): void;
}
Better:
interface BurgerOrderService {
orderBurger(quantity: number): void;
}
interface FriesOrderService {
orderFries(quantity: number): void;
}
interface ComboOrderService {
orderCombo(burgers: number, fries: number): void;
}
Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
Enables swapping implementations (e.g., database change) without modifying business logic.
Foundation for achieving OCP and SRP in practice.