Single Responsibility Principle in Java with Examples
Last Updated :
15 Mar, 2023
SOLID is an acronym used to refer to a group of five important principles followed in software development. This principle is an acronym of the five principles which are given below…
- Single Responsibility Principle (SRP)
- Open/Closed Principle
- Liskov’s Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
In this post, we will learn more about the Single Responsibility Principle. As the name indicates, it states that all classes and modules should have only 1 well-defined responsibility. As per Robert C Martin,
A class should have one, and only one reason to change.
This means when we design our classes, we need to ensure that our class is responsible only for 1 task or functionality and when there is a change in that task/functionality, only then, that class should change.
In the world of software, change is the only constant factor. When requirements change and when our classes do not adhere to this principle, we would be making too many changes to our classes to make our classes adaptable to the new business requirements. This could involve lots of side effects, retesting, and introducing new bugs. Also, our dependent classes need to change, thereby recompiling the classes and changing test cases. Thus, the whole application will need to be retested to ensure that new functionality did not break the existing working code.
Generally in long-running software applications, as and when new requirements come up, developers are tempted to add new methods and functionality to the existing code which makes the classes bloated and hard to test and understand. It is always a good practice to look into the existing classes and see if the new requirements fit into the existing class or should there be a new class designed for the same.
Benefits of Single Responsibility Principle
- When an application has multiple classes, each of them following this principle, then the applicable becomes more maintainable, easier to understand.
- The code quality of the application is better, thereby having fewer defects.
- Onboarding new members are easy, and they can start contributing much faster.
- Testing and writing test cases is much simpler
Examples
In the java world, we have a lot of frameworks that follow this principle. JSR 380 validation API is a good example that follows this principle. It has annotations like @NotNull, @Max, @Min, @Size which are applied to the bean properties to ensure that the bean attributes meet the specific criteria. Thus, the validation API has just 1 responsibility of applying validation rules on bean properties and notifying with error messages when the bean properties do not match the specific criteria
Another example is Spring Data JPA which takes care of all the CRUD operations. It has one responsibility of defining a standardized way to store, retrieve entity data from persistent storage. It eases development effort by removing the tedious task of writing boilerplate JDBC code to store entities in a database.
Spring Framework in general, is also a great example of Single Responsibility in practice. Spring framework is quite vast, with many modules - each module catering to one specific responsibility/functionality. We only add relevant modules in our dependency pom based on our needs.
Let's look at one more example to understand this concept better. Consider a food delivery application that takes food orders, calculates the bill, and delivers it to customers. We can have 1 separate class for each of the tasks to be performed, and then the main class can just invoke those classes to get these actions done one after the other.
Java
import java.io.*;
import java.util.*;
class GFG {
public static void main(String[] args)
{
Customer customer1 = new Customer();
customer1.setName("John");
customer1.setAddress("Pune");
Order order1 = new Order();
order1.setItemName("Pizza");
order1.setQuantity(2);
order1.setCustomer(customer1);
order1.prepareOrder();
BillCalculation billCalculation
= new BillCalculation(order1);
billCalculation.calculateBill();
DeliveryApp deliveryApp = new DeliveryApp(order1);
deliveryApp.delivery();
}
}
class Customer {
private String name;
private String address;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getAddress() { return address; }
public void setAddress(String address)
{
this.address = address;
}
}
class Order {
private Customer customer;
private String orderId;
private String itemName;
private int quantity;
private int totalBillAmt;
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer)
{
this.customer = customer;
}
public String getOrderId() { return orderId; }
public void setOrderId(String orderId)
{
Random random = new Random();
this.orderId = orderId + "-" + random.nextInt(500);
}
public String getItemName() { return itemName; }
public void setItemName(String itemName)
{
this.itemName = itemName;
setOrderId(itemName);
}
public int getQuantity() { return quantity; }
public void setQuantity(int quantity)
{
this.quantity = quantity;
}
public int getTotalBillAmt() { return totalBillAmt; }
public void setTotalBillAmt(int totalBillAmt)
{
this.totalBillAmt = totalBillAmt;
}
public void prepareOrder()
{
System.out.println("Preparing order for customer -"
+ this.getCustomer().getName()
+ " who has ordered "
+ this.getItemName());
}
}
class BillCalculation {
private Order order;
public BillCalculation(Order order)
{
this.order = order;
}
public void calculateBill()
{
/* In the real world, we would want a kind of lookup
functionality implemented here where we look for
the price of each item included in the order, add
them up and add taxes, delivery charges, etc on
top to reach the total price. We will simulate
this behaviour here, by generating a random number
for total price.
*/
Random rand = new Random();
int totalAmt
= rand.nextInt(200) * this.order.getQuantity();
this.order.setTotalBillAmt(totalAmt);
System.out.println("Order with order id "
+ this.order.getOrderId()
+ " has a total bill amount of "
+ this.order.getTotalBillAmt());
}
}
class DeliveryApp {
private Order order;
public DeliveryApp(Order order) { this.order = order; }
public void delivery()
{
// Here, we would want to interface with another
// system which actually assigns the task of
// delivery to different persons
// based on location, etc.
System.out.println("Delivering the order");
System.out.println(
"Order with order id as "
+ this.order.getOrderId()
+ " being delivered to "
+ this.order.getCustomer().getName());
System.out.println(
"Order is to be delivered to: "
+ this.order.getCustomer().getAddress());
}
}
OutputPreparing order for customer -John who has ordered Pizza
Order with order id Pizza-57 has a total bill amount of 46
Delivering the order
Order with order id as Pizza-57 being delivered to John
Order is to be delivered to: Pune
We have a Customer class that has customer attributes like name, address. Order class has all order information like item name, quantity.
The BillCalculation class calculates the total bill sets the bill amount in the order object. The DeliveryApp has 1 task of delivering the order to the customer. In the real world, these classes would be more complex and might require their functionality to be further broken down into multiple classes.
For example, the bill calculation logic might require some kind of lookup functionality to be implemented where we look for the price of each item included in the order against some kind of database, add them up, add taxes, delivery charges, etc and finally reach the total price. Depending on how complex the code starts to become, we might want to move the taxes, database queries etc, to other separate classes. Similarly, the delivery class might want to interface with another task management system that actually assigns the task of delivery to different delivery agents based on location, shift timings, whether that delivery person has actually shown up to work, etc. These individual steps could move to separate classes when they need specialized handling.
If the functionality of bill calculation, as well as order delivery, was added in the same class, then that class gets modified whenever the bill calculation logic or the delivery agent logic needs to change; which goes against the Single Responsibility Principle. As per the example, we have a separate class for handling each of these functions. Any single business requirement change should ideally have an impact on only one class, thus catering to the Single Responsibility Principle.
Similar Reads
DRY (Donât Repeat Yourself) Principle in Java with Examples
DRY is simply an approach, or we can say, a different perspective to programmers. DRY stands for Don't Repeat Yourself. In Java, it means donât write the same code repeatedly. Suppose you are having the same code at many places in your program, then it means you are not following the DRY approach; Y
5 min read
Reactive Programming in Java with Example
Java reactive programming refers to a programming paradigm that focuses on building responsive and scalable applications that can handle concurrent and asynchronous tasks efficiently. Reactive programming is based on the principles of the reactive manifesto, which includes characteristics such as re
6 min read
Open Closed Principle in Java with Examples
In software development, the use of object-oriented design is crucial. It helps to write flexible, scalable, and reusable code. It is recommended that the developers follow SOLID principles when writing a code. One of the five SOLID principles is the open/closed principle. The principle states that
9 min read
Java Relational Operators with Examples
Operators constitute the basic building block to any programming language. Java too provides many types of operators which can be used according to the need to perform various calculations and functions, be it logical, arithmetic, relational, etc. They are classified based on the functionality they
10 min read
Java Singleton Design Pattern Practices with Examples
In previous articles, we discussed about singleton design pattern and singleton class implementation in detail. In this article, we will see how we can create singleton classes. After reading this article you will be able to create your singleton class according to your requirement, which is simple
6 min read
Period plus() method in Java with Examples
The plus() method of Period class in Java is used to add the given amount of period to the specified period. This functions operates separately on YEAR, MONTH and DAY. Note: Normalization is not performed. 12 months and 1 year are different. Syntax: public Period plus(TemporalAmount amountToAdd) Par
2 min read
Optional get() method in Java with examples
The get() method of java.util.Optional class in Java is used to get the value of this Optional instance. If there is no value present in this Optional instance, then this method throws NullPointerException. Syntax: public T get() Parameters: This method do not accept any parameter. Return value: Thi
2 min read
Field toString() method in Java with Examples
The toString() method of java.lang.reflect.Field is used to get a string describing this Field. The format of the returned string is the access modifiers for the field, if any, followed by the field type, followed by a space, followed by the fully-qualified name of the class declaring the field, fol
3 min read
Protected Keyword in Java with Examples
Access modifiers in Java help to restrict the scope of a class, constructor, variable, method, or data member. There are four types of access modifiers available in java. The access of various modifiers can be seen in the following table below as follows:Â The protected keyword in Java refers to one
5 min read
Static Method in Java With Examples
In Java, the static keyword is used to create methods that belongs to the class rather than any specific instance of the class. Any method that uses the static keyword is referred to as a static method.Features of Static Method:A static method in Java is associated with the class, not with any objec
3 min read