DEV Community

Cover image for Single Responsibility Principle
Subham
Subham

Posted on • Edited on

2

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.

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more