One Reason to Change
The Single Responsibility Principle is the S in SOLID, five rules for designing code that stays easy to change. It says: a type should have one job — and therefore only one reason to change.
When one class saves orders and sends emails, it changes when the storage changes and when the email text changes. Two jobs, two reasons, twice the risk of breaking one thing while touching the other.
In TypeScript the rule applies to classes, their methods, and modules: keep each one focused on a single task.
Too Many Jobs in One Type
This order processor works — but it stores the order, writes the confirmation text, and "sends" it, all in one method. Testing the email text alone is impossible, and reusing the storage part elsewhere means dragging the email code along.
One type that does everything
process() mixes two jobs: storing and notifying. Any change to either one means editing — and re-testing — this same class.
Output: TypeScript file and captured output
class OrderProcessor {
// process does two jobs: storing AND notifying.
process(order: string, customer: string): void {
console.log("saving order to storage:", order)
console.log("sending email to " + customer + ":")
console.log(" Thank you! Your order", order, "was received.")
}
}
const processor = new OrderProcessor()
processor.process("#1042 (2 books)", "[email protected]")
One Job per Type
The fix: one class per job. OrderStore only stores, EmailSender only sends. A small function coordinates them.
The output is the same — but now each part can be changed, tested, and reused on its own.
Split into two types
Same behavior, separated: the store knows nothing about emails, the sender knows nothing about storage. The processOrder function just connects them.
Output: TypeScript file and captured output
// OrderStore has one job: storing orders.
class OrderStore {
save(order: string): void {
console.log("saving order to storage:", order)
}
}
// EmailSender has one job: sending emails.
class EmailSender {
sendConfirmation(customer: string, order: string): void {
console.log("sending email to " + customer + ":")
console.log(" Thank you! Your order", order, "was received.")
}
}
function processOrder(order: string, customer: string): void {
const store = new OrderStore()
const sender = new EmailSender()
store.save(order)
sender.sendConfirmation(customer, order)
}
processOrder("#1042 (2 books)", "[email protected]")
The payoff: reuse without fear
Because the sender is its own class, it sends a shipping notice too — no storage code involved. And the store archives an order without composing a single email. Each class grew inside its one job.
Output: TypeScript file and captured output
class OrderStore {
save(order: string): void {
console.log("saving order to storage:", order)
}
archive(order: string): void {
console.log("moving order to the archive:", order)
}
}
class EmailSender {
sendConfirmation(customer: string, order: string): void {
console.log("email to " + customer + ": order", order, "was received")
}
sendShippingNotice(customer: string, order: string): void {
console.log("email to " + customer + ": order", order, "was shipped")
}
}
const store = new OrderStore()
const sender = new EmailSender()
// Each class grows inside its own job.
sender.sendShippingNotice("[email protected]", "#1042")
store.archive("#0997")