shreyansh

Single Responsibility Principle

Introduction

The Single Responsibility Principle (SRP) states that a class should have only one reason to change. In other words, a class should have only one responsibility.

Let's look at a class which is not following this principle:

import java.util.List;

class Invoice {
    private List<Product> products;

    public Invoice(List<Product> products) {
        this.products = products;
    }

    public double calculateTotal() {
        return products.stream().mapToDouble(Product::getPrice).sum();
    }

    public void printInvoice() {
        products.forEach(product -> System.out.println(product.getDescription() + ": " + product.getPrice()));
        System.out.println("Total: " + calculateTotal());
    }
    
    public void saveToDB(){
	    // saves to DB..
    }
}
class Product {
    private double price;
    private String description;

    public Product(double price, String description) {
        this.price = price;
        this.description = description;
    }

    public double getPrice() {
        return price;
    }

    public String getDescription() {
        return description;
    }
}

It seems to have several responsibilities:

  1. Calculating the total amount - If the pricing logic changes, the Invoice class would need to be modified.
  2. Saving data to a database - If you decide to change the database technology or the way data is saved, the Invoice class would need to be modified.
  3. Printing the invoice - If the printing mechanism changes (e.g., from a physical print to digital format), you would need to modify the Invoice class.

Let's segregate these responsibilities in separate classes.

Invoice.java

import java.util.List;

class Invoice {
    private List<Product> products;

    public Invoice(List<Product> products) {
        this.products = products;
    }

    public double calculateTotal() {
        return products.stream().mapToDouble(Product::getPrice).sum();
    }

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

Product.java

class Product {
    private double price;
    private String description;

    public Product(double price, String description) {
        this.price = price;
        this.description = description;
    }

    public double getPrice() {
        return price;
    }

    public String getDescription() {
        return description;
    }
}

InvoiceCalculator.java

public class InvoiceCalculator {
    private Invoice invoice;

    public InvoiceCalculator(Invoice invoice) {
        this.invoice = invoice;
    }

    public int calculateTotal() {
		return products.stream().mapToDouble(Product::getPrice).sum();
    }
}

InvoicePersistence.java

class InvoicePersistence {
    public void saveToDatabase(Invoice invoice) {
        // Logic to save invoice details to a database
    }
}

InvoicePrinter.java

public class InvoicePrinter {
    private Invoice invoice;

    public InvoicePrinter(Invoice invoice) {
        this.invoice = invoice;
    }

    public void printInvoice() {
        // Print the invoice
    }
}

Each class has a single responsibility and can be developed, tested, and modified independently.