Links
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 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
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.