Published on

The Open/Closed Principle (OCP)

5 min read - 811 words
Authors
  • avatar
    Name
    Cédric RIBALTA
    Twitter
post image

Introduction

As a web developer (front or back), you have probably already encountered situations where a simple change in the code caused bugs in other parts of the system.

This often happens when the code is not designed to be scalable or modular.

A girl doing boom with her hands

This is where our Open/Closed Principle (OCP) comes into play, which is 1 of the 5 fundamental principles of the SOLID code concept.

We will break down this principle together and see how it can make your code more robust, scalable, and easier to maintain.


What is the Open/Closed Principle (OCP)?

This principle states that: software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

This means that you should be able to add new features to a codebase without modifying its existing source code.

In other words, you should be able to extend the behavior of the code without touching the lines of code that have already been tested and validated.


Why is this principle so important for our code?

In the world of web/software development, requirements often change very quickly.

A new feature is required, a third-party service changes its API, or clients request new behaviors.

If your code is built in a monolithic way, you will constantly have to modify it to meet these changes, increasing the risk of bugs and making the system harder to maintain.

By following the OCP, you design your code so that it is extensible without touching the existing parts.

This leads to more modular web applications, where each component is independent and feature evolutions won’t create bugs throughout the entire application.


How to apply the OCP in practice?

Let's see how to apply the Open/Closed Principle in your code.

1. Use abstractions:

One way to make your code extensible is to structure it with interfaces or abstract classes. This allows you to define general behavior that other entities can implement.

For example: you have a system that manages different payment methods (credit card, PayPal, etc.), you can create an interface Payment that defines common methods like pay().

Each specific payment method (CreditCard, PayPal, etc.) implements this interface, allowing you to easily add a new payment method without touching the existing code.

interface Payment {
  pay(amount: number): void
}

class CreditCard implements Payment {
  pay(amount: number): void {
    console.log(`Payment of ${amount} using a credit card.`)
  }
}

class PayPal implements Payment {
  pay(amount: number): void {
    console.log(`Payment of ${amount} using PayPal.`)
  }
}

// Adding a new payment method
class CryptoCurrency implements Payment {
  pay(amount: number): void {
    console.log(`Payment of ${amount} using cryptocurrency.`)
  }
}

2. Avoid direct modifications of existing code:

Rather than modifying existing classes or functions, create extensions or derivatives that add the new behavior. This keeps the original code intact while adding new features.

3. Favor composition over excessive inheritance:

In some cases, instead of extending a class via inheritance, it’s often better to compose your objects.

For example, by injecting dependencies that implement certain interfaces. This makes the modules more flexible and less coupled, allowing you to easily add new behaviors.

Example with an e-commerce site:

Suppose your site calculates shipping costs based on the country. Initially, you only have two types of fees: domestic and international.

If one day you need to add options like express shipping or region-specific deliveries, you may need to modify several parts of your code.

By following the OCP, you can design a system where shipping cost calculation is based on extensible strategies without modifying the existing code.

You define a ShippingCost interface and implement specific classes for each type of delivery.

Here is a code example:

interface ShippingCost {
  calculate(): number
}

class DomesticShipping implements ShippingCost {
  calculate(): number {
    return 5.0 // fixed cost for domestic shipping
  }
}

class InternationalShipping implements ShippingCost {
  calculate(): number {
    return 20.0 // fixed cost for international shipping
  }
}

// Adding a new express shipping option
class ExpressShipping implements ShippingCost {
  calculate(): number {
    return 15.0 // cost for express shipping
  }
}

Thanks to this structure, you can add as many shipping options as necessary without touching the existing code. You simply need to create a new class that implements the interface.

The advantages of OCP in your code:

  • Reduction of bug risks: Since you don’t need to modify the existing code, you reduce the risk of introducing regressions or unintended bugs.

  • Increased modularity: Each new feature is encapsulated in an independent entity, which improves code readability and maintenance.

  • Ease of evolution: By applying the OCP, your code becomes more easily adaptable to future changes, a valuable asset in a web environment where requirements evolve rapidly.

Conclusion:

The Open/Closed Principle is a central pillar of good development practices, especially in a field as fast-changing as web development. By following this principle, you make your code more extensible, more reliable, and much easier to maintain.

As a developer, applying the OCP allows you to better manage the complexity of your applications and offer cleaner, more solid code capable of withstanding the changing demands of the modern web.