Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Shades of the Single Responsibility Principle

DZone's Guide to

Shades of the Single Responsibility Principle

It's not always easy to tell whether code violates an age-old rule, like the Single Responsibility Principle. Take a look at a blurrier situation and see how you'd handle it.

· Java Zone
Free Resource

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

In the object-oriented world, each developer who wants to live long and prosper should know the SOLID rules. SOLID is just an acronym of basic tips for writing maintainable, readable code. We all know many developers who follow these rules. And we all know many who know them, but don't use them. And finally, plenty of programmers have heard something about SOLID, but don't bother to learn anything about it. However well we use it, we can agree that sticking to SOLID is a good practice. But there are always compromises.

In fact, in the real world, we have to know that each letter in SOLID has own shades and gradients — they are not simply white or black. In this article, I want to tell you something about the Single Responsibility Principle (SRP), which covers the first letter in our acronym. What is it?

Single Responsibility Principle is one of the few main tips in object-oriented design introduced by Robert C. Martin. He said:  "A class should have only one reason to change." Simple, verbose, and powerful. Another definition of this rule, which has the same meaning but can be easier to understand is, "Every class should doing one specific thing and not trying
to do more than that."
 Now that, we've gotten a refresher on the definition, let's look at an example:

public class Person {

    private String firstName;
    private String secondName;
    private Double salaryInDollars;

    public Double getSalaryInEuro() {
        return salaryInDollars * CurrencyUtil.getCurrentDollarToEuroRate();
    }

    public Double getSalaryInYen() {
        return salaryInDollars * CurrencyUtil.getCurrentDollarToYenRate();
    }

    //getters and setters
}


At first glance, we can see that something is wrong. Why should the Person class know something about cash exchange? What if we want another currency? Of course, the example here is easy, and so is the solution: We must remove getSalaryIn methods from Person and create another class named Cantor (or SalaryExchanger/SalaryCalculator/CurrencyConverter, etc.) which has knowledge of exchange rates. Now, Person objects provide salary to Cantor, Cantor takes the number in, calculates this number using a ratio, and pushes it out.

Sometimes it is easier to divide responsibilities and sometimes it is harder, but we know the concept now. The main problem is that we must find a line between one accountability and another.

Consider that we have another Person class (which is a simple POJO that comes from a REST endpoint by JSON) and a service layer between the endpoint and the DB, which is responsible for managing Person objects. We have separate methods for CRUD operations. I created the interface, and the implementation of the interface below should do the mapping and call the proper operation on the DB (by using another repository level):

//It is responsible for manipulating Person between endpoint layer and database layer

public interface PersonService {

    void deletePerson(String id);

    void updatePerson(String id, Person person);

    Person getPerson(String id);

    Person createPerson(Person person);

}


Stop here. Look at that interface once again and ask the question, "Does the class I described have a single responsibility?"

This is a problem I mention before — where can we say that the implementation violates SRP and where doesn't it? Of course, it depends on the person who looks at that code. One can say, "The service has one responsibility because manipulating Person before we do some DB calls is one responsibility." Meanwhile, another might say, "I can see four responsibilities, so it violates the principle." In that case, the second programmer will probably suggest another design:

//It is responsible for delete person in DB
public interface PersonDeleterService {

    void deletePerson(String id);

}

//It is responsible for update person in DB
public interface PersonUpdaterService {

    void updatePerson(String id, Person person);

}

//It is responsible for get person from DB
public interface PersonReaderService {

    Person getPerson(String id);

}

//It is responsible for save person in DB
public interface PersonCreatorService {

    Person createPerson(Person person);

}


I can see advantages and disadvantages in each point of view. The first solution provides a solid API — we know what we want and do not spread ourselves too thin. We have an interface, we have an implementation, we have tests — that is all. On the other hand, at the start of the implementation, our class will have approximately 70 lines of code, which is acceptable. But if we have new requirements, our methods will grow. It's the same thing for the test class — moderate in the beginning, but very large after new requirements are factored in.

Because the second solution gives us the flexibility to use only the functionality we want, changes will be easier to maintain. The code will be more readable because each class will have fewer lines of code. The same goes for test classes. However, we have to create many more little pieces and write many lines of code and text. If we have more than one table in the DB (and in most cases, we will) we have to create four or more classes for each one, resulting in BIG packages. Little problem, big solution.

Which is better? I don't know. As I said there is a fine line. Personally, I will vote for and use in my future development second solution, but I don't see big problems with first.

In such cases like this, which strongly depends on subjective opinions, I think that it's a hairline gap between good design and code smell. Where is the frontier? Where do we draw a line in the usage of the Single Responsibility Principle?

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:
java ,solid ,srp ,single responsibility principle

Published at DZone with permission of Mateusz Winnicki. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}