Single Responsibility Principle
Tags: systemdesign, javascript, typescript
Published: 2024-04-27 01:42:48

## **Links**

[**REPO**](https://github.com/Subham-Maity/scalable-system-design)
[**Linkedin**](https://www.linkedin.com/in/subham-xam/)
[**Github**](https://github.com/Subham-Maity)

**Info** : [Single-responsibility principle - Wikipedia](https://en.wikipedia.org/wiki/Single-responsibility_principle)


**The Single Responsibility Principle (SRP) is one of the SOLID principles of object-oriented programming. It states that a class should have only one reason to change. In other words, a class should have only one responsibility or job.**

```tsx
node dist/srp.js
```

### **Each class should focus on a single responsibility or task.**

## **Example: E-commerce Order System**

- Imagine an e-commerce platform with an order processing system.
- The system handles various tasks:
    - Product management
    - Order creation
    - Pricing calculation
    - Invoice generation
    - Payment processing

```tsx
|---srp.ts 
|---order
    |---Order.ts
    |---Product.ts   
    |---Jobs
         |---invoice.ts
         |---PaymentProcessor.ts
         |---PricingCalculator.ts
```

## **Breaking Down Responsibilities**

- **Product Class (`Product.ts`):**
    - Responsible for representing product details (ID, name, price).
- **Order Class (`Order.ts`):**
    - Manages the list of products in an order.
    - Adds and retrieves products.
- **Invoice Class (`invoice.ts`):**
    - Generates an invoice for an order.
    - Displays product names and prices.
- **PaymentProcessor Class (`PaymentProcessor.ts`):**
    - Handles payment processing.
    - Sends emails and updates accounting.
- **PricingCalculator Class (`PricingCalculator.ts`):**
    - Calculates the total price of an order.

``order/Product.ts``

```tsx
export class Product {

    constructor(id: string, name: string, price: number) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    id : string;
    name : string;
    price : number;
}
```

``order/Order.ts``

```tsx
import {Product} from "./Product";
export class Order {
    product: Product [] = []

    addProduct(product: Product) {
        this.product.push(product)
    }

    getProduct() {
        return this.product
    }
}
```

``order/Jobs/invoice.ts``

```tsx
import {Product} from "../Product";

export class Invoice {

    generateInvoice(product: Product[] , amount: number) {
        console.log(`
        Invoice Date : ${new Date().toLocaleString()}
        _____________________________
        
        Product Name\t\t\tPrice
        `);

        product.forEach((product:Product)=> {
            console.log(`${product.name}\t\t\t${product.price}`)
        });

        console.log(`_____________________________`);
        console.log(`Total Price : ${amount}`)
    }
}
```

``order/Jobs/PaymentProcessor.ts``

```tsx
import {Order} from "../Order";

export class PaymentProcessor {
    processPayment(order: Order) {
        console.log(`Processing payment...`)
        console.log(`Payment processed successfully.`)
        console.log(`Added to accounting system!`)
        console.log(`Email sent to customer!`)
    }
}
```

``order/Jobs/PricingCalculator.ts``

```tsx
import {Product} from "../Product";

export class PricingCalculator {
    calculatePricing(products: Product[]): number {
        return products.reduce((acc, product) => acc + product.price, 0);
    }
}
```

## **Exceptions and Violations**

- **Exceptions:**
    - Sometimes, combining responsibilities is necessary for efficiency.
    - For example, tightly coupling pricing calculation and order management might be acceptable.
- **Violations:**
    
    ```tsx
    // Product class representing product details
    class Product {
        constructor(public id: string, public name: string, public price: number) {}
    }
    
    // Imagine an Order class that handles both order management and payment processing
    class Order {
        private orderID: string;
        private products: Product[];
    
        constructor(orderID: string) {
            this.orderID = orderID;
            this.products = [];
        }
    
        addProduct(product: Product) {
            this.products.push(product);
        }
    
        calculateTotalPrice(): number {
            return this.products.reduce((total, product) => total + product.price, 0);
        }
    
        processPayment(paymentMethod: string) {
            // Process payment logic here
            console.log(`Payment for order ${this.orderID} processed via ${paymentMethod}`);
        }
    }
    
    // Usage
    const product1 = new Product("1", "Laptop", 200000);
    const product2 = new Product("2", "Phone", 60000);
    
    const order = new Order("123");
    order.addProduct(product1);
    order.addProduct(product2);
    
    const total = order.calculateTotalPrice();
    console.log(`Total price: ${total}`);
    
    order.processPayment("Credit Card");
    ```
    
    - The `Order` class combines two distinct responsibilities: managing the list of products (order management) and processing payments.
    - Violation: If payment processing logic changes, it impacts the `Order` class, which should focus only on order management.
    - Solution: Separate payment processing into a dedicated class (e.g., `PaymentProcessor`). Each class should have a clear purpose to adhere to SRP.

``srp.ts``

```tsx
import {Product, Order, PricingCalculator, Invoice ,PaymentProcessor} from "./order";

const product1 = new Product("1","Laptop", 200000);
const product2 = new Product("2","Phone", 60000);
const product3 = new Product("3", "Car", 8000000);

const order = new Order();

order.addProduct(product1);
order.addProduct(product2);
order.addProduct(product3);

const pricingCalculator = new PricingCalculator();
const total = pricingCalculator.calculatePricing(order.getProduct());

const invoice = new Invoice();
invoice.generateInvoice(order.getProduct(), total);

const paymentProcessor = new PaymentProcessor();
paymentProcessor.processPayment(order);
```

**Summary**

- **SRP ensures that each class has a clear purpose.**
- **By separating concerns, we improve maintainability and reduce the impact of changes.**
- **In our e-commerce example, adhering to SRP leads to a more robust and flexible system.**