Java Programming Tutorials

Java programming tutorials with many code examples!

Single Responsibility Principle explained

Single Responsibility Principle (SRP) is the most important of SOLID software design principles. In this post we explain it and show how to apply!

TL;DR:

“A class should have one, and only one, reason to change.”

SRP in small pieces

Single– there should be only one responsibility;
Responsibility – reason of change (it doesn’t mean that a class should have only one method, but that its members should be focused on its primary function);
Principle – it’s not a physics or math, rather look at it as a rule of thumb.

Code tells the story

Instead of 1000 words let’s jump directly into Java code (Don’t worry if you don’t speak Java. The code is understandable for anybody who knows what a programming language is.), which will illustrate when Single Responsibility Principle is violated and how to fix it to make the world a better place. :-)

SRP violation in REST/Web Controllers

The first example is a controller, which could be a REST controller or any web controller. The common case is that it always starts to only serve data from a database. Nothing else. After a few months the controller is handling web stuff (conversion to/from JSON/XML, security, and many other things), business logic, talking to db, etc. So, here’s such simple version of a funky controller…

Before Single Responsibility Principle

The following code is simplified, but still very similar to what can be seen in many “enterprise” systems (I’ve seen a web controller with around 4500 lines of code…):

package com.farenda.solid.srp;

import com.farenda.solid.srp.Player.Position;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.List;

public class TeamController {

    private final PlayerRecruiter recruiter;

    public TeamController(PlayerRecruiter recruiter) {
        this.recruiter = recruiter;
    }

    public Team createTeam(String teamName, String attribute) {
        // Responsibility 1 - loading existing players:
        List<Player> players = new LinkedList<>();
        try {
            List<String> playersData = loadPlayersFromFile();
            // Parse CSV player data:
            for (String line : playersData) {
                if (line.startsWith("#")) {
                    continue;
                }
                String[] fields = line.split(",");
                Player player = new Player();
                player.setName(fields[0]);
                player.setPosition(Position.valueOf(fields[1].trim()));
                player.setPoints(Byte.valueOf(fields[2].trim()));
                player.setAssists(Byte.valueOf(fields[3].trim()));

                players.add(player);
            }
        } catch (IOException | URISyntaxException e) {
            throw new RuntimeException(e);
        }

        // Responsibility 2 - business logic:
        if (recruiter.hasPlayers()) {
            players.add(recruiter.draft());
        }

        // Find best 5 players:
        Sorter sorter = new Sorter(attribute);
        sorter.sort(players);
        if (players.size() > 5) {
            players.subList(5, players.size()).clear();
        }

        return new Team(teamName, players);
    }

    public void saveTeam(Team team) {
        // Save into CSV file:
        try {
            CSVExporter csv = new CSVExporter(team.getName());
            csv.write(team.getPlayers());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private List<String> loadPlayersFromFile()
        throws URISyntaxException, IOException {
        //TODO load from a DB in the next release!
        URI datasetUri = TeamController.class.getClassLoader()
                .getResource("player-data.csv").toURI();
        return Files.readAllLines(Paths.get(datasetUri),
                                  StandardCharsets.UTF_8);
    }
}

This controller has two public methods – one for creating new teams, the other for saving changes in teams. The problem is that the controller is responsible for business logic (how teams are created) and how to load and store data. Both responsibilities change independently. Is it easy to understand the code? This case is simple, so it’s not that hard. Is it easy to test and maintain? No.

Single Responsibility Principle applied

How can we make the code better? In such situations I almost always start with Composed Method Pattern. In around 30 secs cohesive blocks of code go into methods that name them and then the responsibilities are ready to be moved. So, loading data and saving go into own class (TeamRepository) that will be responsible for persistence. How it will do that? The controller doesn’t care. Another responsibility we can move into a separate class is selection of players to the team. Let’s make other object responsible for that – here SelectionStrategy. Now, the only responsibility of the controller is coordination of work between collaborators – TeamRepository, PlayerRecruiter, and SelectionStrategy.

package com.farenda.solid.srp;

import java.util.List;

public class TeamController {

    private final PlayerRecruiter recruiter;
    private final TeamRepository repository;
    private final SelectionStrategy selectionStrategy;

    public TeamController(PlayerRecruiter recruiter,
                          TeamRepository repository,
                          SelectionStrategy selectionStrategy) {
        this.recruiter = recruiter;
        this.repository = repository;
        this.selectionStrategy = selectionStrategy;
    }

    public void saveTeam(Team team) {
        repository.save(team);
    }

    public Team createTeam(String teamName, String attribute) {
        // Actions on the same level of abstraction:
        List<Player> players = loadExistingPlayers();
        recruitNew(players);
        selectBest(players, attribute);
        return new Team(teamName, players);
    }

    private List<Player> loadExistingPlayers() {
        return repository.loadPlayers();
    }

    private void recruitNew(List<Player> players) {
        if (recruiter.hasPlayers()) {
            players.add(recruiter.draft());
        }
    }

    private void selectBest(List<Player> players,
                            String attribute) {
        selectionStrategy.selectBest(players, attribute);
    }
}

After refactoring to the Single Responsibility Principle the code became much simpler. Each of collaborators participating in providing the functionality of the controller is very easy to understand, test, and change. Is it easy to understand how creation of team works? You can understand it in a few secs! Is it easy to test? No problem – mock or stub collaborators and that’s it. Want to use a database for persistence? The controller doesn’t even care!

SRP violation in a business logic

In this example we’ll show simple, nevertheless very common violation of SRP.

Before Single Responsibility Principle

The following class implements functionality of a university coach. It’s job is to prepare players for recruitment to a professional league (you can think of this as uni coach that prepares students for NBA draft):

package com.farenda.solid.srp;

import com.farenda.solid.srp.Player.Position;

import java.util.Random;

public class UniversityCoach {

    private Random random = new Random();

    private PlayerRecruiter recruiter;

    public UniversityCoach(PlayerRecruiter recruiter) {
        this.recruiter = recruiter;
    }

    public void trainNewPlayer() {
        // responsibility 1 - how to create a player:
        Player player = new Player();
        player.setName("Random Joe #" + random.nextInt(100));
        player.setPoints((byte) random.nextInt(50));
        player.setPosition(pickRandomPosition());
        player.setAssists((byte) random.nextInt(20));

        // responsibility 2 - prepare player for draft:
        player.practice();

        // the coach knows when a player is ready, so he submits:
        recruiter.submit(player);
    }

    private Position pickRandomPosition() {
        int posId = random.nextInt(Position.values().length);
        return Position.values()[posId];
    }
}

The problem is that the coach, besides preparing players (his job) is also responsible for creating them! These are two things that may (and most probably will) change and for different reasons:

  1. New training strategy.
  2. How, when, if at all players should be instantiated.

The second point changes so frequently that all of GOF Creational Patterns deal with this problem – leave the responsibility of creation to those who specialize in that.

Single Responsibility Principle applied

Of course, the simplest thing we can do is to extract creation of players to another class. This way we’ll have one class responsible for creation of new players (let’s call it PlayerFactory) and the other one – UniversityCoach – responsible for preparation of the players:

package com.farenda.solid.srp;

import java.util.Random;

import com.farenda.solid.srp.Player.Position;

// Knows how to create new players.
public class PlayerFactory {

    private Random random = new Random();

    public Player newPlayer() {
        Player player = new Player();
        player.setName("Random Joe #" + random.nextInt(100));
        player.setPoints((byte) random.nextInt(50));
        player.setPosition(pickRandomPosition());
        player.setAssists((byte) random.nextInt(20));
        return player;
    }

    private Position pickRandomPosition() {
        int posId = random.nextInt(Position.values().length);
        return Position.values()[posId];
    }
}

And the new, simplified coach:

package com.farenda.solid.srp;

// Knows how to prepare players.
public class UniversityCoach {

    private PlayerFactory playerFactory;
    private PlayerRecruiter recruiter;

    public UniversityCoach(PlayerFactory playerFactory,
                           PlayerRecruiter recruiter) {
        this.playerFactory = playerFactory;
        this.recruiter = recruiter;
    }

    public void trainNewPlayer() {
        Player player = playerFactory.newPlayer();

        player.practice();

        recruiter.submit(player);
    }
}

Should he use PlayerFactory to create new players? Probably not, but that is another case. The important thing is he is not responsible for the way players are created. Now if we would like to create them in a different way, we can simply replace factory with our own (especially in tests) without changes to the coach. Similarly if we would like to change training strategy it would not affect the way players are created. The impact of changes is constrained.

Cons of mixed responsibilities

Classes with many responsibilities, more often than not, have a number of problems:

  • Usually they are harder to test, because they have more dependencies. Writing tests for such code is tedious (recall creating 10 mocks only to make the test pass) and very often only a happy path is covered. This leads to bugs.
  • They are harder to understand, because you have to dissect more unrelated things and remember how your code plays with its dependencies. This clearly leads to bugs.
  • Mixed responsibilities are coupled and can cause unwanted, mutual changes. You change a class for one reason, but code handling some other responsibility in this class might have been affected, so you need to test modules/applications that are using this class, but for completely different reasons.
  • Increase probability of conflicts when working on source code. Developers may want to modify the same class, but for different reasons – one is developing one feature that affects one responsibility, but another one is fixing a bug in the same class, but in the code handling a different responsibility. Who will push own changes first – wins, the other one will have to spend a few hours on merging – sounds familiar? ;-)

Sometimes I hear from inexperienced developers that refactoring to SRP leads to many small classes, which solves one problem, but creates another. Well, the truth is that Single Responsibility Principle creates more small classes, but this can be easily handled by proper use of packages/modules/libraries.

Pros of Single Responsibility Principle

Classes and modules conforming to SRP have these properties:

  • They are easy to understand, because they do only one thing.
  • At the same time the code is much simpler to test, because the flow is simpler and it doesn’t have many collaborators.
  • Code easy to understand and test is also maintainable.
  • Easy to refactor (move around or even delete).

These properties are especially important for developers who write software in corporations. Most software there is legacy and refactoring code to SRP makes the work less dull and allows to modify code faster (write new, fix bugs, refactor).

How to introduce SRP

AFAIK the biggest challenge for those who want to start apply SOLID principles is identification of responsibilities. There are quite a few ways to do that, which we’ll show here.

Describe what a method/class/module is doing

Simple, no? The thing is that not many people actually do that. Nowadays we write “self documenting code” and comments are prohibited. However, just as an exercise, take one of your classes and try to describe in one sentence what it suppose to do: “The responsibility of this class is …”. This should be one, short sentence. If you find yourself using and or or then there’s high probability that the class/module is doing too much – not always true, but it’s usually the case.

Refactor class to Composed Method

This is my favourite method. The Composed Method Pattern extracts cohesive parts of code to separate methods that name what they do. This easily allows to identify different responsibilities in the class – one method is handling dates, another is formatting output, etc. Such code can be moved to separate classes with properly defined responsibilities.

Use Test-Driven Development

TDD naturally leads to separation of responsibilities. When writing a test, you can quickly see that tests for the same class test completely different things, sometimes from different business areas.

What next?

Find a class in your project and ask yourself these questions:

  • What is (are?) its responsibility?
  • Are there more than one?
  • Would refactoring to SRP help? How?
  • How to introduce SRP here?

Resources

Sometime ago I wrote a post about Software Design Books. In the context of SRP two of them are especially important: “Agile Principles, Patterns, and Practices” by Robert C. Martin, and “Applying UML and Patterns” by Craig Larman. Study at least one of them and your software won’t be the same anymore. ;-)

Share with the World!