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
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();
}
}