Single Responsibility Principle

RMAG news

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

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

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;
}

order/Order.ts

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

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

getProduct() {
return this.product
}
}

order/Jobs/invoice.ts

import {Product} from ../Product;

export class Invoice {

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

Product NametttPrice
`);

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

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

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!`)
}
}

order/Jobs/PricingCalculator.ts

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:

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

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.

Leave a Reply

Your email address will not be published. Required fields are marked *