Skip to main content

Single Responsibility Principle (SRP)

What is the Single Responsibility Principle?​

Shubh: Hey, have you heard of the Single Responsibility Principle?

You: I've heard about it, but I'm not entirely sure what it means. Can you explain it?

Shubh: Absolutely! The Single Responsibility Principle (SRP) is one of the SOLID principles of object-oriented design. It states that a class should have only one reason to change, meaning it should have only one job or responsibility.

Understanding the Problem: A Bad Approach​

To grasp SRP, let's look at a scenario where SRP is not followed, which makes the code harder to maintain.

Shubh: Imagine we have an Order class that not only manages products but also handles invoice generation and payment processing. Here's how it might look:

Product.java
public class Product {
private String id;
private String name;
private int price;

public Product(String id, String name, int price) {
this.id = id;
this.name = name;
this.price = price;
}

//getters & setters
}
Order.java
public class Order {
private final List<Product> products = new ArrayList<>();

Order(){}

public void addProduct(Product product){
products.add(product);
}

public void removeProduct(String productId){
products.removeIf(product -> product.getId().equals(productId));
}

public Product getProduct(String productId){
Optional<Product> optionalProduct = products.stream()
.filter(product -> product.getId().equals(productId))
.findFirst();
return optionalProduct.orElse(null);
}

public List<Product> getProducts(){
return products;
}

public int calculatePrice(){
int sum = 0;
for(var product : products){
sum += product.getPrice();
}
return sum;
}

public void generateInvoice(){
System.out.println("Invoice Date : " + new Date());
System.out.println("---------------------------");
System.out.println("Product Name\tPrice");

for(var product : products){
System.out.println(product.getName() + "\t\t" + product.getPrice());
}
System.out.println("---------------------------");
System.out.println("Total : " + calculatePrice());
}

public void processPayment(){
System.out.println("Processing Payment...");
System.out.println("Payment Processed Successfully");
System.out.println("Email Sent to Customer");
}
}
Main.java
public class Main {
public static void main(String[] args) {
Product product1 = new Product("1", "MacBook", 100000);
Product product2 = new Product("2", "Iphone", 70000);

Order myOrders = new Order();
myOrders.addProduct(product1);
myOrders.addProduct(product2);

myOrders.calculatePrice();
myOrders.generateInvoice();
myOrders.processPayment();
}
}

You: I see. The Order class is doing too much. It's managing products, calculating prices, generating invoices, and processing payments.

Shubh: Exactly! This violates the SRP because the Order class has multiple reasons to change. For instance, if the invoice format changes, we have to modify the Order class. If the payment processing changes, we have to modify the Order class again. This makes the code harder to maintain and understand.

A Better Approach: Applying the Single Responsibility Principle​

To improve this design, we need to apply the Single Responsibility Principle by separating the different responsibilities into distinct classes.

You: How can we refactor the code to follow SRP?

Shubh: Here's how we can restructure the code:

  • The Order class will focus solely on managing products.

  • The Invoice class will handle invoice generation.

  • The PaymentProcessor class will handle payment processing.

  • The PricingCalculator class will handle price calculation.

Product.java
public class Product {
private String id;
private String name;
private int price;

public Product(String id, String name, int price) {
this.id = id;
this.name = name;
this.price = price;
}

//getters & setters
}
Order.java
public class Order {
private final List<Product> products = new ArrayList<>();

Order(){}

public void addProduct(Product product){
products.add(product);
}

public void removeProduct(String productId){
products.removeIf(product -> product.getId().equals(productId));
}

public Product getProduct(String productId){
Optional<Product> optionalProduct = products.stream()
.filter(product -> product.getId().equals(productId))
.findFirst();
return optionalProduct.orElse(null);
}

public List<Product> getProducts(){
return products;
}
}
PaymentProcessor.java
public class PaymentProcessor {
public void processPayment(){
System.out.println("Processing Payment...");
System.out.println("Payment Processed Successfully");
System.out.println("Email Sent to Customer");
}
}
PricingCalculator.java
public class PricingCalculator {
public int calculatePrice(List<Product> products){
int sum = 0;
for(var product : products){
sum += product.getPrice();
}
return sum;
}
}
Main.java
public class Main {
public static void main(String[] args) {
Product product1 = new Product("1", "MacBook", 100000);
Product product2 = new Product("2", "Iphone", 70000);

Order myOrders = new Order();
myOrders.addProduct(product1);
myOrders.addProduct(product2);

Invoice invoice = new Invoice(new PricingCalculator());
invoice.generateInvoice(myOrders.getProducts());

PaymentProcessor paymentProcessor = new PaymentProcessor();
paymentProcessor.processPayment();
}
}

You: I see! Now the Order class only manages the list of products, the Invoice class generates the invoice, the PaymentProcessor handles the payment processing, and the PricingCalculator calculates the total price. Each class has a single responsibility.

Shubh: Exactly! By applying the SRP, we make our code easier to maintain and extend. Each class has a clear purpose, and changes to one responsibility won't affect others.

You: This makes a lot of sense. Thanks for explaining SRP with such a clear example!