SOLID Principles Explained: Single Responsibility Principle (SRP)
Your code can look clean and still violate SRP. Let's discover why using a simple e-commerce example in TypeScript.
SOLID Principles Explained #1: Single Responsibility Principle (SRP)

A common mistakes a developer do when learning SRP is thinking it means “A class should do only one thing”.
This is not what it means.
A class should have only once reason to change.
The idea is simple: group things that change for the same reason, separate things that change for different reasons. If two pieces of logic change due to completely different business decisions, they dont dont belong in the same class.
The Problem
Imagine you’re working on an e-commerce application.
Your Order class manages products, calculates pricing, generates invoices, and processes payments.
Everything looks fine initially.
But a few months later, the finance team changes pricing rules, the accounting team requests a new invoice format, and the business switches payment providers.
Suddenly, the same class keeps changing for completely unrelated reasons.
This is exactly the problem the Single Responsibility Principle tries to solve.
Here’s a Product class and Order class that handles everything.
export class Product {
id: string
price: number
name: string
constructor(id: string, price: number, name: string) {
this.id = id
this.price = price
this.name = name
}
}
export class Order {
products: Product[] = []
addProduct(product: Product) {
this.products.push(product);
}
removeProduct(id: string) {
this.products = this.products.filter(product => product.id !== id);
}
getProducts() {
return this.products;
}
calculatePricing() {
return this.products.reduce((total, product) => total + product.price, 0)
}
generateInvoice() {
console.log(`
Invoice Date: ${new Date().toDateString()}
------------------------------------------
Product name\tPrice
`);
this.products.forEach((product: Product) => {
console.log(`${product.price}`)
})
console.log(`------------------------------------------`);
console.log(`Total: ${this.calculatePricing()}`)
}
processPayment() {
console.log('Payment Processing.........')
console.log('Payment Processed Successfully');
console.log('Added to accounting system');
console.log('Email sent to customer ')
}
}
Let’s analyze this by asking one question
What are the different reasons Order class might change ?
Responsibility 1: Managing the Order
addProduct()
removeProduct()
getProdcut()
These methods are mainting the state of the order. If tomorrow the business logic says:
- An order can have atmost 100 products.
- A product must be in stock before it can be added to order
These methods change.
This is one responsibility:
Order Management
Responsibility 2: Pricing Logic
calculatePricing()
This feels related to an order, but it changes for completely different reason. What if the business wants to:
- Support Coupons
- Add Discounts
- Add Shiping charges
Now this method changes, but the order management logic doesn’t. That’s a sign they belong apart.
This responsibility is:
Pricing / Billing Rules
Responsibility 3: Invoice Generation
generateInvoice()
Right now the invoice shows
+----------------------+
| Product Name | Price |
+----------------------+
But what if business wants
+---------------------------------------------+
| Product Name | Qty | Tax | Discount | Price |
+---------------------------------------------+
Or a PDF invoice, HTML invoice.
The invoice format changed — but the order and pricing logic didn’t touch.
Completely separate responsibility:
Invoice Generation
Responsibility 4: Payment Processing
processPayment()
If business switches from Razorpay to Paypal, this method changes. But the order itself? Untouched.
This is its own business concern:
Payment Processing
The Fix: Onc Class, One Reason
After decomposing Order, here’s what we get:
export class Order {
products: Product[] = []
addProduct(product: Product) {
this.products.push(product);
}
removeProduct(id: string) {
this.products = this.products.filter(product => product.id !== id);
}
getProducts() {
return this.products;
}
}
export class PricingCalculator {
calculateTotalPrice(products: Product[]): number {
return products.reduce((total, product) => total + product.price, 0);
}
}
export class Invoice {
generateInvoice(products: Product[], total: number) {
console.log(`
Invoice Date: ${new Date().toDateString()}
------------------------------------------
Product name\tPrice
`);
products.forEach((product: Product) => {
console.log(`${product.price}`)
})
console.log(`------------------------------------------`);
console.log(`Total: ${total}`)
}
}
export class PaymentProcessing {
processPayment() {
console.log('Payment Processing.........')
console.log('Payment Processed Successfully');
console.log('Added to accounting system');
}
}
Now each class has exactly one reason to change:
Orderchanges when order management rules changePricingCalculatorchanges when pricing rules changeInvoicechanges when invoice requirements changePaymentProcessingchanges when payment workflows change
Why This Matters
SRP isn’t about splitting code for the sake of it. It’s about making sure that when a business decision changes, you know exactly which class to touch — and you’re confident that touching it won’t break something unrelated.
That’s what makes codebases maintainable as they grow.
메타데이터
- post_id
- 46152d08d14e
- slug
- solid-principles-explained-single-responsibility-principle-srp-46152d08d14e
- url
- https://medium.com/@devisrisaicharan2/solid-principles-explained-single-responsibility-principle-srp-46152d08d14e
- canonical_url
- https://medium.com/@devisrisaicharan2/solid-principles-explained-single-responsibility-principle-srp-46152d08d14e
- author_url
- https://medium.com/@devisrisaicharan2
- status
- ok
- fetched_at
- 2026-07-29 11:52:52