SOLID and Single Responsibility principle

Overview

In this series of articles, I will cover all the SOLID patterns and their use in examples.

The SOLID principles were introduced by Robert C. Martin in his 2000 paper “Design Principles and Design Patterns.”

The SOLID principles consist of the following five concepts:

Single Responsibility Principle
Open/Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle

srp

Single Responsibility Principle

A class should have only a single responsibility (i.e. only one potential change in the software’s specification should be able to affect the specification of the class)

So, what is wrong in here?

public class PaymentCalculator {

    public BigDecimal calculatePay(Employee e) throws InvalidEmployeeType {
            switch (e.getType()) {
                case COMMISSIONED:
                    return calculateCommissionedPay(e);
                case HOURLY:
                    return calculateHourlyPay(e);
                case SALARIED:
                    return calculateSalariedPay(e);
                default:
                    throw new InvalidEmployeeType(e.getType());
            }
    }
}
public enum EmployeeType {
    COMMISSIONED, HOURLY, SALARIED
}


public class Employee {
    private EmployeeType type;

    public Employee(EmployeeType type) {
        this.type = type;
    }

    public EmployeeType getType() {
        return type;
    }
}

When you focus on switch you can see that this switch is responsible for more than one thing. First of all, it is decision-making. Secondly the result of that decision is calculated. And that is a violation of the first principle of SOLID, that all classes should have only one responsibility. Because when I want to make changes to a calculation, I only want to change the calculation class, but I do not want to change the decision logic and vice versa.

Solution

There are multiple way how to solve that violation. The most recommended solution is the keep switch.

First I created interface where is defined calculate method.

public interface Calculation {
    BigDecimal calculate();
}

I will then implement this interface using different compute classes, each of which will only be responsible for this one case.

public class ComissionedPayCalculation implements Calculation {

    @Override
    public BigDecimal calculate() {
        return NotImplementYet();
    }

}

public class HourlyPayCalculation implements Calculation {

    @Override
    public BigDecimal calculate() {
        return NotImplementYet();
    }

}
public class SalariedPayCalculation implements Calculation {

    @Override
    public BigDecimal calculate() {
        return NotImplementYet();
    }

}

I then created a factory where the old switch lives and is only responsible for the decision (the decision returns a class that does the math for us), but the switch only returns the result of the decision, not the result itself.

public class CalculationFactory {
    static Calculation createCalculation(Employee employee) {
        switch (employee.getType()) {
            case COMMISSIONED:
                return new ComissionedPayCalculation();
            case HOURLY:
                return new HourlyPayCalculation();
            case SALARIED:
                return new SalariedPayCalculation();
            default:
                throw new InvalidEmployeeType(employee.getType());
        }
    }
}

// And finally calling of this factory can looks where only calculation is done

public class PaymentCalculator {
    public BigDecimal calculatePay(Employee employee) throws InvalidEmployeeType {

        return CalculationFactory.createCalculation(employee).calculate();
    }
}
Michal Slovík
Michal Slovík
Java developer and Cloud DevOps

My job interests include devops, java development and docker / kubernetes technologies.