DEV Community

Cover image for Single Responsibility Principle
Subham
Subham

Posted on • Edited on

Single Responsibility Principle

Links

REPO
Linkedin
Github

Info : Single-responsibility principle - Wikipedia

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.

node dist/srp.js
Enter fullscreen mode Exit fullscreen mode

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
|---srp.ts 
|---order
    |---Order.ts
    |---Product.ts   
    |---Jobs
         |---invoice.ts
         |---PaymentProcessor.ts
         |---PricingCalculator.ts
Enter fullscreen mode Exit fullscreen mode

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

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;
}
Enter fullscreen mode Exit fullscreen mode

order/Order.ts

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

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

    getProduct() {
        return this.product
    }
}
Enter fullscreen mode Exit fullscreen mode

order/Jobs/invoice.ts

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}`)
    }
}
Enter fullscreen mode Exit fullscreen mode

order/Jobs/PaymentProcessor.ts

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!`)
    }
}
Enter fullscreen mode Exit fullscreen mode

order/Jobs/PricingCalculator.ts

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

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

Exceptions and Violations

  • Exceptions:
    • Sometimes, combining responsibilities is necessary for efficiency.
    • For example, tightly coupling pricing calculation and order management might be acceptable.
  • Violations:

    // 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

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);
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)