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.