Open Closed Principle

Insurances Example

We’ll start with a design that violates the Open Closed Principle, which forces us to modify and add code to a class every time we want to add a new insurance type. Then we’ll improve on it to a (better) design that follows the Open Closed Principle, which enables us to add new features without modifying existing code.

Initial (flawed) Design

public class HealthInsuranceCustomerProfile {
  public boolean isLoyalCustomer() {
    return true;
  }
}

public class InsurancePremiumDiscountCalculator {
  public int
  calcPremiumDiscountPercent(HealthCustomerInsuranceProfile customer) {
    if (customer.isLoyalCustomer()) {
      return 20;
    }

    return 0;
  }
}

Note that calcPremiumDiscountPercent() method takes a HealthInsuranceCustomerProfile object.

@startuml
skinparam DefaultFontName Source Code Pro
skinparam DefaultFontSize 15

class InsurancePremiumDiscountCalculator {
  +calcPremiumDiscountPercent(HealthInsuranceCustomerProfile customer): void
}

class HealthCustomerInsuranceProfile {
  +isLoyalCustomer(): boolean
}

InsurancePremiumDiscountCalculator ..> HealthCustomerInsuranceProfile : uses
@enduml

What if we need to calculate discount for another type of insurance?

public class VehicleInsuranceCustomerProfile {
  public boolean isLoyalCustomer() {
    return true;
  }
}

@startuml
skinparam DefaultFontName Source Code Pro
skinparam DefaultFontSize 15

class InsurancePremiumDiscountCalculator {
  +calcPremiumDiscountPercent(HealthInsuranceCustomerProfile customer): void
  +calcPremiumDiscountPercent(VehicleInsuranceCustomerProfile customer): void
}

note top of InsurancePremiumDiscountCalculator
We have to **overload**
calcPremiumDiscountPercent() to take
the the new type as argument.
endnote

class HealthCustomerInsuranceProfile {
  +isLoyalCustomer(): boolean
}

class VehicleCustomerInsuranceProfile {
  +isLoyalCustomer(): boolean
}

InsurancePremiumDiscountCalculator ..> HealthCustomerInsuranceProfile : uses
InsurancePremiumDiscountCalculator ..> VehicleCustomerInsuranceProfile : uses
@enduml

What if we add house insurance, life insurance, and other types of insurances? With the current design, we have to keep creating new overloads for the calcPremiumDiscountPercent() method for each new type of insurance.

To add a new feature, we have to keep adding code to the existing InsurancePremiumDiscountCalculator class, violating the Open Closed Principle. The existing code is supposed to be closed for modification (it is OK to create new classes or other things, though).

Improved Design

We’ll add an interface CustomerProfile to the design, keep only one calcPremiumDiscountPercent() method, and make the insurance classes implement that interface.

@startuml
skinparam DefaultFontName Source Code Pro
skinparam DefaultFontSize 15
top to bottom direction

interface CustomerProfile {
  +isLoyalCustomer(): boolean
}

class HealthCustomerInsuranceProfile implements CustomerProfile {
  +isLoyalCustomer(): boolean
}

class VehicleCustomerInsuranceProfile implements CustomerProfile {
  +isLoyalCustomer(): boolean
}

InsurancePremiumDiscountCalculator .up.> HealthCustomerInsuranceProfile : uses
InsurancePremiumDiscountCalculator .up.> VehicleCustomerInsuranceProfile : uses

class InsurancePremiumDiscountCalculator {
  +calcPremiumDiscountPercent(CustomerProfile customer): int
}
@enduml

With this design, whenever we want to add new insurance types, we simply create new classes that implement CustomerProfile, not even needing to touch the the calculator class.

Benefits of OCP

  • Easy of adding new features;

  • Minimal cost of testing and developing new features;

  • Decouples the design. By applying Open Closed Principle, we also “unwittingly” also abode by the Single Responsibility Principle. That is, to follow OCP, we implicitly also end up following SRP.

    • Applying OCP is a subjective decision, rather than an objective one.

WARNING: Use OCP judiciously. Don’t blindly apply this principle to every design. It will sometimes create a huge number of classes or components that may make the entire design more complex and harder (not easier) to maintain. Use your best judgement.