Open-Closed Principle
Tags: codequality, agile, beginners, codenewbie
Published: 2021-06-09 22:41:02

The **Open-Closed Principle** states that classes must remain open for extension but closed for modification.

>Classes must remain open for extension but closed for modification.

This means that if we want to add more functionality to a class we should do it by extending the behavior of the class without actually changing the class itself. This may seem pretty confusing at first, but will make sense with the code example.

The objective of this principle is to avoid modifications that may lead to bugs where the class is being used.

#Example

Let's imagine that we are developing a Checkout functionality for a store that offers discounts to `Senior` clients and `Pregnant` clients. At first, our base code will be the following:

```js
class Checkout {
  getTotalToPay(total, clientType) {
    let totalToPay = total;
    let discount = 0;

    if (clientType === 'Senior') {
      discount = total * 0.25;
      totalToPay = totalToPay - discount;
    } else if (clientType === 'Pregnant') {
      discount = total * 0.15;
      totalToPay = totalToPay - discount;
    }
    return totalToPay;
  }
}

const checkout = new Checkout();
console.log(checkout.getTotalToPay(100, 'Senior'));
console.log(checkout.getTotalToPay(100, 'Pregnant'));
console.log(checkout.getTotalToPay(100, 'Regular Client'));
```
By looking at the code you may think that everything is okay, but the reality is we are breaking the **Open-Closed Prinicple**. This is because, even though the class is **open** for extension as we may have as many client types as we like, it is not **closed** for modification. 

Let's say that we want to add the `Veteran` type of client. To do this, we would simply need to add another `else if` to our code. The resulting code will be the following:

```js
class Checkout {
  getTotalToPay(total, clientType) {
    let totalToPay = total;
    let discount = 0;

    if (clientType === 'Senior') {
      discount = total * 0.25;
      totalToPay = totalToPay - discount;
    } else if (clientType === 'Pregnant') {
      discount = total * 0.15;
      totalToPay = totalToPay - discount;
    } else if (clientType === 'Veteran') {
      discount = total * 0.10;
      totalToPay = totalToPay - discount;
    }
    return totalToPay;
  }
}

const checkout = new Checkout();
console.log(checkout.getTotalToPay(100, 'Senior'));
console.log(checkout.getTotalToPay(100, 'Pregnant'));
console.log(checkout.getTotalToPay(100, 'Veteran'));
console.log(checkout.getTotalToPay(100, 'Regular Client'));
```
Since we had to modify our code to allow this new type of client, we broke the **Closed** part of the principle.

To adhere to the principle, we may split every type of client into a class and handle the checkout calculation by abstraction. The refactoring of the code would be the following:

```js
class Checkout {
  getTotalToPay(total, clientType) {
    let discount = clientType.getDiscount(total);
    let totalToPay = total - discount;
    return totalToPay;
  }
}

class SeniorClient {
  getDiscount(total) {
    return total * 0.25;
  }
}

class PregnantClient {
  getDiscount(total) {
    return total * 0.15;
  }
}

class VeteranClient {
  getDiscount(total) {
    return total * 0.1;
  }
}

class RegularClient {
  getDiscount(total) {
    return total * 0;
  }
}
const checkout = new Checkout();
console.log(checkout.getTotalToPay(100, new SeniorClient()));
console.log(checkout.getTotalToPay(100, new PregnantClient()));
console.log(checkout.getTotalToPay(100, new VeteranClient()));
console.log(checkout.getTotalToPay(100, new RegularClient()));
```
By splitting each client type into different classes, we don't need to touch the Checkout class again. If we wanted to add another client type we would only need to create it as a new class and pass it to the `getTotalToPay` function, following the **Open-Closed Principle**.