Single Responsibility Principle (SRP)
Adam Roynon
Posted on April 11, 2020
The Single Responsibility Principle (SRP) is one of the aspects of The SOLID Principles. It states that every class or object should have one purpose, one responsibility and effectively one reason for existence. This principle is concerned mainly with object-orientated programming but it can and should be thought about at a deeper granularity. Every function or method should have one reason to exist, do one thing and only one. It should do one thing and do that one thing well.
Following the SRP can help you keep your code maintainable, flexible and easy to read. Imagine being able to read your code and understand exactly what it is doing without having to run the code or read every line completely. This will make the code easier to refactor and generally just easier to deal with.
Simple Example
Let's look at very simple to identify an example. It can be quite difficult to identify an entity that is going against the SRP. We start with a simple Square class that holds one field, the side length of the square. There are two methods in this class, the getter, and setter for our one field. It is clear that this class has one responsibility and therefore follows the SRP. It is responsible for holding the one field 'sideLength'.
public class Square {
private int sideLength;
public int getSideLength(){
return sideLength;
}
public void setSideLength(int sideLength){
this.sideLength = sideLength;
}
}
Now we add a method called 'getArea' that returns the area of the square. We have added additional functionality to our class, so now it can do two things. You may think that this now goes against the SRP as it has two things it is responsible for, the side length and calculating the area. However, if we change the abstraction of our SRP context we can see we are still following the rule. Instead of the class being responsible for looking after one field, it now looks after our "Square" shape and the fields and operations we need for that object. It is responsible for handling Square shapes. It is important to note that each method in our class is responsible for one thing, our "getArea" method doesn't do anything else apart from returning the calculated area.
public class Square {
private int sideLength;
public int getSideLength(){
return sideLength;
}
public void setSideLength(int sideLength){
this.sideLength = sideLength;
}
public int getArea(){
return sideLength * sideLength
}
}
Now it's time to break the single responsibility principle. We have added a method to our square class called 'isUserAuthenticated' which will check is a user is authenticated with our system. It is clear that this method is out of place, it doesn't belong in this class as it has nothing to do with our Square object. This new method has broken the SRP in the context of our class.
public class Square {
private int sideLength;
public int getSideLength(){
return sideLength;
}
public void setSideLength(int sideLength){
this.sideLength = sideLength;
}
public boolean isUserAuthenticated(User user){
// Do something here
return false;
}
}
Real-Life Example
The previous example was pretty obvious when we broke the single responsibility principle. Now let's look at a more real-life scenario that you might deal with in a real codebase or on the job. The below class User shows a bunch of fields that correspond to a user; their username, password, first and last name, date of birth, address, etc. Technically this is following the SRP as it only deals with stuff to do with a user, there is nothing strange or out of context in this class. However, if we look closer and think about what other code might be inside this class we could argue differently.
public class User {
private String username;
private String password;
private String firstName;
private String lastName;
private Date dob;
private String addressFirstLine;
private String addressSecondLine;
private String Country;
private String addressPostcode;
private String contactNumber;
private String emailAddress;
// Getters & Setters
// + validation
}
We could argue that the address portions of the class are to do with something else, they have a different context. We could move these out into a new class called Address. We could argue the same thing with the contact number and email address fields as they're to do with ContactDetails. These are simple ways we can reduce the scope of our class while still keeping true to the SRP.
With each of these fields, we may have validation, to ensure a strong password or a valid email address. The validation of these fields could be moved to a separate location, as validation is a different responsibility for holding the information. We might also have database code within this class, to save and retrieve Users from our database. This code could also be moved outside of the class. As you can tell the SRP can get very complicated when dealing with real-world examples, but it is important to think about what your class's purpose is and extract any other code. Is it's purpose to hold information about the user, to validate a certain field, to handle database interactions, to handle logging?
Spring AOP
When dealing with logging and security in code it can be hard to follow the Single Responsibility Principle. You need to ensure that an unauthenticated user cannot access this piece of code, so you wrap it in an authentication check. You need to log out to a file when a new database item is created so you throw some logging lines around the code. These sorts of examples go against the SRP as now your code is handling logging as well as security or database interaction.
Fortunately, if we use Spring, which is a framework for the Java programming language, there exists a module called Aspect-Oriented Programming (AOP). I won't be going into detail about how to use AOP or how the below code works, as I'll save that for another time. AOP allows us to extract out these cross-cutting concerns, such as logging or authentication. A cross-cutting concern is basically some piece of code or functionality that appears across a lot of modules or places in the code. Using AOP we are able to extract out these concerns and still follow the SRP.
@Aspect
public class UserAspect {
private static final Logger LOGGER=LoggerFactory.getLogger(UserAspect.class);
@Before("execution(public String getPassed())")
public void getNameAdvice(){
LOGGER.info("Executing Advice on getPassword()");
}
@Before("execution(* com.acroynon.spring.service.*.get*())")
public void getAllAdvice(){
LOGGER.info("Service method getter called");
}
}
This post was originally published on https://acroynon.com
Posted on April 11, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.