SOLID and Open Closed 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

ocp

Open Closed Principle

A software module (class or method) should be open for extension but closed for modification.

And I reuse code from previous article where I focus on Single Responsibility principle. Only change will be switch to Kotlin instead of Java.

Inheritance

In first exercise I have one interface Calculation which is implemented in various scenarios.

import java.math.BigDecimal

interface Calculation {
    fun calculate(): BigDecimal?
}

class ComissionedPayCalculation : Calculation {
    override fun calculate(): BigDecimal? {
        return null
    }
}

class HourlyPayCalculation : Calculation {
    override fun calculate(): BigDecimal? {
        return null
    }
}

class SalariedPayCalculation : Calculation {
    override fun calculate(): BigDecimal? {
        return null
    }
}

Then I have CalculationFactory where is decision for that scenarios made:

import java.math.BigDecimal

class PaymentCalculator {

    @Throws(InvalidEmployeeType::class)
    fun calculatePay(e: Employee): BigDecimal? {
        return CalculationFactory.createCalculation(e.type).calculate()
    }

}

object CalculationFactory {
    fun createCalculation(type: EmployeeType?): Calculation {
        return when (type) {
            EmployeeType.COMMISSIONED -> ComissionedPayCalculation()
            EmployeeType.HOURLY -> HourlyPayCalculation()
            EmployeeType.SALARIED -> SalariedPayCalculation()
            else -> throw InvalidEmployeeType(type)
        }
    }
}

And I want to add new calculation for SALARIED and HOURLY paid employees, where I want increase salary by 11% as a bonus.

What would be the most advisable solution with respect to the principle of an open closed system?

There are two ways how to implement that new functionality. Firstly we will look at inheritance solution.

In that solution I will create new class which extends from existing HourlyPayCalculation and I call super constructor and multiple by bonus:

class BonusHourlyPayCalculation : HourlyPayCalculation() {
    val BONUS = 1.11

    override fun calculate(): BigDecimal? {
        return super.calculate()?.multiply(BigDecimal(BONUS));
    }
}

class BonusSalariedPayCalculation : SalariedPayCalculation() {
    val BONUS = 1.11

    override fun calculate(): BigDecimal? {
        return super.calculate()?.multiply(BigDecimal(BONUS));
    }
}

However, as you can see in this scenario, the code can grow considerably with each new feature, and this is one of the reasons why inheritance is not a highly recommended solution. A better approach to write less code and fewer classes.

Composition over inheritance

Let’s say I want to have all the decision logic (responsibility) in one place. And CalculationFactory is the best for that. Is there any way to refactor this bonus calculation decision to the factory class ?

Composition can help in that way. Firstly I create new class BonusPayCalculation implements Calculation interface.

class BonusPayCalculation constructor(private val multiplication: BigDecimal,
                                      private val baseCalculation: Calculation) : Calculation {
    override fun calculate(): BigDecimal? {
        return baseCalculation.calculate()?.multiply(multiplication);
    }
}

As you can see there are variables which are consume by constructor, one is multiplication value (bonus in this case) and baseCalculation which can be any implementation what we want. And in factory I made this change:

object CalculationFactory {
    val BONUS_VALUE = 1.11
    fun createCalculation(type: EmployeeType?): Calculation {
        return when (type) {
            EmployeeType.COMMISSIONED -> ComissionedPayCalculation()
            EmployeeType.HOURLY -> BonusPayCalculation(BigDecimal.valueOf(BONUS_VALUE), HourlyPayCalculation());
            EmployeeType.SALARIED -> BonusPayCalculation(BigDecimal.valueOf(BONUS_VALUE), SalariedPayCalculation());
            else -> throw InvalidEmployeeType(type)
        }
    }
}

So the CalculationFactory is also responsible for deciding the value of the BONUS and which class to use for each payment calculation, and if I want to add some new functionality in the future (e.g. calculating payment for external), BonusPayCalculation will remain untouched and will be able to use my new functionality.

Michal Slovík
Michal Slovík
Java developer and Cloud DevOps

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