Skip to main content

Solid Principles

Open closed principle

O — OCP — Open-closed principle


Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.You should be able to use and add to a module without rewriting it.

This means that existing code should not be modified to add new functionality, but rather should be extended through the addition of new code. By following the OCP, code can be made more flexible and maintainable, and can avoid the introduction of bugs and errors that can occur when existing code is modified.

Why It’s Important:

  • Maintainability: Frequent changes to existing code can introduce bugs and make it harder to understand and manage.
  • Flexibility: OCP allows you to extend functionality without disrupting existing code, making it easier to adapt to new requirements.
  • Testability: Well-defined, closed modules are easier to test independently, ensuring reliability as the system evolves.

How to Achieve It:


  • Use interfaces, abstract classes, or base classes to define contracts for behaviour.
  • Concrete implementations can then fulfill these contracts without modifying the core abstractions.

Dependency Injection:

  • Pass dependencies to objects through constructors or methods, rather than hardcoding them.
  • This allows you to swap implementations easily without changing the consuming code.

Design Patterns:

  • Patterns like Strategy, Template Method, and Decorator often embody OCP principles.
  • They provide flexible ways to extend behavior without modifying existing code.

Examples in object oriented programming

Bad code if we want to add new payment method we need to modify processOrder function.

class OrderProcessor {
    public void processOrder(Order order) {
        // ... order validation

        if (order.getPaymentMethod().equals("credit_card")) {
            processCreditCardPayment(order); // Directly handles credit card payment
        } else if (order.getPaymentMethod().equals("paypal")) {
            processPayPalPayment(order); // Directly handles PayPal payment
        } else {
            throw new UnsupportedOperationException("Unsupported payment method");

        // ... order fulfillment logic

    // ... methods for specific payment processing logic


Tightly Coupled: OrderProcessor directly handles multiple payment methods, creating strong dependencies.
Not Open for Extension: Adding new payment methods requires modifying processOrder().
Closed for Modification: Changes to payment processing logic might break existing code.

How to fix this?

Adhering to open closed principle.

interface PaymentProcessor {
    void processPayment(Order order);

class CreditCardProcessor implements PaymentProcessor {
    // ... implementation for credit card processing

class PayPalProcessor implements PaymentProcessor {
    // ... implementation for PayPal processing

class OrderProcessor {
    private PaymentProcessor paymentProcessor;

    public OrderProcessor(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;

    public void processOrder(Order order) {
        // ... order validation
        paymentProcessor.processPayment(order); // Delegate payment processing
        // ... order fulfillment logic


Abstraction: PaymentProcessor interface defines a contract for payment processing.
Dependency Injection: OrderProcessor receives the appropriate PaymentProcessor implementation, decoupling them.
Open for Extension: New payment methods can be added by implementing PaymentProcessor, without modifying OrderProcessor.
Closed for Modification: Existing OrderProcessor code remains stable even with new payment methods.

More examples of code which follows open closed principle

ShapeCalculator is closed for modification—it doesn't need changes to handle new shapes.
It's open for extension—new shapes can be added by implementing the Shape interface.

interface Shape {
    double calculateArea();

class Circle implements Shape {
    // ... implementation for Circle

class Rectangle implements Shape {
    // ... implementation for Rectangle

class ShapeCalculator {
    public double calculateTotalArea(List<Shape> shapes) {
Application is open for extension—new logging strategies can be added by implementing Logger.
It's closed for modification—no changes to Application are needed to switch logging strategies.

interface Logger {
    void log(String message);

class ConsoleLogger implements Logger {
    // ... writes logs to the console

class FileLogger implements Logger {
    // ... writes logs to a file

class Application {
    private Logger logger;

    public Application(Logger logger) {
        this.logger = logger;

    public void doSomething() {
        // ... application logic
        logger.log("Something happened!");

How can follow open-closed principle in functional programming?

In the functional programming world, achieving this is possible using hook functions. By passing new functions, we can alter the default behavior without directly modifying the original function. Consider the saveRecord function, which takes a record as input and saves it. Now, envision future requirements for this system:

  • What if we need to perform actions before the save function?
  • What if there are tasks to execute after the save function?
  • What if changes in the logic of the save function are required?

Using hook functions enables flexibility in addressing these potential scenarios without directly altering the core functionality of saveRecord.

function saveRecord(record) {
  // code to save the record

Let’s write a functional programming code which follows open close principle

const saveRecord = (record, before = () => {}, after = () => {}, saveFn = defaultSave) => {
  before(); // Execute before-save logic
  saveFn(record); // Call the provided save function
  after(); // Execute after-save logic

const defaultSave = (record) => {
  // Default save functionality

Open for Extension:
The saveFn argument allows for customizing the save behavior without modifying saveRecord itself.
You can pass different save implementations as needed, adhering to OCP.
Closed for Modification:
The core logic within saveRecord remains unchanged, ensuring stability and reducing risk of unintended side effects.
Default Behavior:
The defaultSave function provides a fallback for cases where no custom saveFn is provided.