← Back to list

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.

Charan · 2026-06-20 11:50 · 1 claps · 2.8 min read
#software-engineering #solid-principles #objectorientedprogramming #typescript #clean-code
Open on Medium ↗
Wiki topics: 💻 · Programming 🌐 · Web Development

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:

  1. An order can have atmost 100 products.
  2. 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:

  1. Support Coupons
  2. Add Discounts
  3. 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:

  • Order changes when order management rules change
  • PricingCalculator changes when pricing rules change
  • Invoice changes when invoice requirements change
  • PaymentProcessing changes 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