Europe: +41 78 715 83 09 - Asia: +84 ‎975 112 112
contact@finix.asia

Blog

2 Jan 2018

SOLID: Open-Close Principle

//
Comments0

Technologies used:   Kotlin 1.2.10 | Maven 3.3.9 | Spek 1.1.5

As applications evolve, changes are required. Changes are required when a new functionality is added or an existing functionality is updated in the application. Often in both situations, you need to modify the existing code, and that carries the risk of breaking the application’s functionality. For good application design and the code writing part, you should avoid change in the existing code when requirements change. Instead, you should extend the existing functionality by adding new code to meet the new requirements. You can achieve this by following the Open Closed Principle.

The rules are:

  1. Open for extension “: This means that the behavior of a software module, say a class can be extended to make it behave in new and different ways. It is important to note here that the term “extended ” is not limited to inheritance using the Java extends keyword. As mentioned earlier, Java did not exist at that time. What it means here is that a module should provide extension points to alter its behavior. One way is to make use of polymorphism to invoke extended behaviors of an object at runtime.
  2. Closed for modification “: This means that the source code of such a module remains unchanged.

Bad Example

Let’s take the example from Spring Gruru Blog. In his example of Open Close principle SG use the example of an Insurance with two classes: HealthInsuranceSurveyor class responsible to validate claims and a ClaimApprovalManager class responsible to approve claims. Both of this classes respect the Single Responsibility Principles:

abstract class InsuranceSurveyor {
    abstract val isValidClaim: Boolean
}

And

class ClaimApprovalManager {
    fun processClaim(surveyor: InsuranceSurveyor) {
        if (surveyor.isValidClaim()) {
            println("ClaimApprovalManager: Valid claim. Currently processing claim for approval....")
        }
    }

}

Both classes are perfectly designed and work fine until we add a new requirement for VehicleInsuranceSurveyor class to validate Vehicle claims.

But, what we also need is to modify the ClaimApprovalManager class to process vehicle insurance claims. This is how the modified ClaimApprovalManager will be:

class ClaimApprovalManager {
    fun processHealthClaim(surveyor: HealthInsuranceSurveyor) {
        if (surveyor.isValidClaim()) {
            println("ClaimApprovalManager: Valid claim. Currently processing claim for approval....")
        }
    }

    fun processVehicleClaim(surveyor: VehicleInsuranceSurveyor) {
        if (surveyor.isValidClaim()) {
            println("ClaimApprovalManager: Valid claim. Currently processing claim for approval....")
        }
    }
}

In the example above, we modified the ClaimApprovalManager class by adding a new processVehicleClaim( ) method to incorporate a new functionality (claim approval of vehicle insurance).

As apparent, this is a clear violation of the Open Closed Principle. We need to modify the class to add support for a new functionality. In fact, we violated the Open Closed Principle in the very first instance we wrote the ClaimApprovalManager class. This may appear innocuous in the current example, but consider the consequences in an enterprise application that needs to keep pace with fast-changing business demands. For each change, you need to modify, test, and deploy the entire application. That not only makes the application fragile and expensive to extend but also makes it prone to software bugs.

Coding Open Closed Principle

We should have written this example as follow:

abstract class InsuranceSurveyor {
    abstract val isValidClaim: Boolean
}

And next we write a class for each Survey

class HealthInsuranceSurveyor : InsuranceSurveyor() {
    /*Logic to validate health insurance claims*/ val isValidClaim: Boolean
        get() {
            println("HealthInsuranceSurveyor: Validating health insurance claim...")
            return true
        }
}

And

class VehicleInsuranceSurveyor : InsuranceSurveyor() {
    /*Logic to validate vehicle insurance claims*/ val isValidClaim: Boolean
        get() {
            println("VehicleInsuranceSurveyor: Validating vehicle insurance claim...")
            return true
        }
}

We wrote the ClaimApprovalManager is now open to support more types of insurance claims and closed for any modifications whenever a new claim type is added

class ClaimApprovalManager {
    fun processClaim(surveyor: InsuranceSurveyor) {
        if (surveyor.isValidClaim()) {
            println("ClaimApprovalManager: Valid claim. Currently processing claim for approval....")
        }
    }
}

Now let’s test it with Spek

package vn.finixasia.didemo.controllers

import org.jetbrains.spek.api.Spek
import org.jetbrains.spek.api.dsl.describe
import org.jetbrains.spek.api.dsl.it
import vn.finixasia.didemo.bean.insurance.HealthInsuranceSurveyor
import vn.finixasia.didemo.bean.insurance.VehicleInsuranceSurveyor

object ClaimApprovalManagerSpec: Spek({
    describe("ClaimApprovalManager tested against different Surveyor") {
        val claim = ClaimApprovalManager()
        it("HealthInsurance") {
            val claim = claim.processClaim(HealthInsuranceSurveyor())
        }
        it("VehicleInsurance") {
            val claim = claim.processClaim(VehicleInsuranceSurveyor())
        }

    }
})

All work as expected.

Leave a Reply

Translate »