Home/Learn/Low Level Design/Open/Closed Principle

Open/Closed Principle

Intermediate
SOLID Principles in Practice

Software entities should be open for extension but closed for modification — add new behavior by writing new code, not changing existing code.

Overview

OCP was coined by Bertrand Meyer and popularized by Robert Martin. The goal is to write code that accommodates new requirements without editing tested, deployed code. The classic mechanism is abstraction: code against interfaces/abstract classes; add new behavior by creating new implementations. Strategy, Template Method, and Decorator patterns are all OCP enablers. In practice, 100% OCP is impossible — some changes require modifying existing code. The pragmatic rule: protect the most volatile extension points with abstractions; accept modification for truly new dimensions.

OCP Violation and Fix with Strategy

A discount calculator with an if-else chain must be modified for every new discount type — violating OCP. Extracting each discount type as a DiscountStrategy implementation makes the calculator open for extension (new strategies) and closed for modification.

Java — OCP with Strategy (Discount Calculator)
// ❌ OCP Violation: modify this class every time a new discount type is added
public class DiscountCalculator {
    public double calculate(Order order) {
        double discount = 0;
        if (order.getType() == OrderType.SEASONAL) {
            discount = order.getTotal() * 0.10;
        } else if (order.getType() == OrderType.EMPLOYEE) {
            discount = order.getTotal() * 0.20;
        } else if (order.getType() == OrderType.BULK) {
            discount = order.getQuantity() > 10 ? order.getTotal() * 0.15 : 0;
        }
        // Adding a new type requires editing this class — OCP violation
        return discount;
    }
}

// ✅ OCP with Strategy pattern
@FunctionalInterface
public interface DiscountStrategy {
    double calculate(Order order);
}

// Each discount type is a new class — no modification needed
public class SeasonalDiscount implements DiscountStrategy {
    @Override public double calculate(Order o) { return o.getTotal() * 0.10; }
}

public class EmployeeDiscount implements DiscountStrategy {
    @Override public double calculate(Order o) { return o.getTotal() * 0.20; }
}

public class BulkDiscount implements DiscountStrategy {
    @Override
    public double calculate(Order o) {
        return o.getQuantity() > 10 ? o.getTotal() * 0.15 : 0;
    }
}

// Closed for modification — never needs to change for new discount types
public class DiscountCalculator {
    private final List<DiscountStrategy> strategies;

    public DiscountCalculator(List<DiscountStrategy> strategies) {
        this.strategies = strategies;
    }

    public double calculate(Order order) {
        return strategies.stream()
                         .mapToDouble(s -> s.calculate(order))
                         .sum();
    }
}

// Adding a loyalty discount: create a new class, register it — zero modification
public class LoyaltyDiscount implements DiscountStrategy {
    @Override public double calculate(Order o) {
        return o.getLoyaltyPoints() > 1000 ? o.getTotal() * 0.05 : 0;
    }
}

OCP with Abstract Class and Template Method

Abstract classes also enforce OCP: the base class defines the algorithm skeleton (closed), while subclasses extend behavior by overriding abstract methods (open).

Java — OCP with Template Method (ReportGenerator)
// Closed base — the report structure never changes
public abstract class ReportGenerator {
    public final String generate(ReportData data) { // final = closed for modification
        String header  = buildHeader(data);
        String body    = buildBody(data);   // abstract = open for extension
        String footer  = buildFooter(data);
        return header + body + footer;
    }

    protected String buildHeader(ReportData data) { return "=== Report ===
"; }
    protected abstract String buildBody(ReportData data);   // must extend
    protected String buildFooter(ReportData data) { return "
=== End ==="; }
}

// Extensions — no modification to ReportGenerator
public class CsvReportGenerator extends ReportGenerator {
    @Override
    protected String buildBody(ReportData data) {
        return data.getRows().stream()
                   .map(row -> String.join(",", row))
                   .collect(Collectors.joining("
"));
    }
}

public class HtmlReportGenerator extends ReportGenerator {
    @Override
    protected String buildBody(ReportData data) {
        StringBuilder sb = new StringBuilder("<table>");
        data.getRows().forEach(row -> {
            sb.append("<tr>");
            row.forEach(cell -> sb.append("<td>").append(cell).append("</td>"));
            sb.append("</tr>");
        });
        return sb.append("</table>").toString();
    }
}

Key Points to Remember

  • 1Open for extension = add new behavior by writing new classes/methods.
  • 2Closed for modification = existing tested code is not changed.
  • 3Strategy and Template Method are the primary OCP enablers in Java.
  • 4The key is identifying the right extension point — abstract over what varies.
  • 5100% OCP is impossible — aim to protect the most volatile variation points.

Interview Questions

Sign in to ask Aria
1

How does Strategy pattern implement the Open/Closed Principle?

MediumAmazon
2

Give a real example where you applied OCP to eliminate a growing if-else chain.

MediumGoogle
3

Is it always possible to follow OCP? What are the trade-offs?

HardMicrosoft
4

How do Java annotations help achieve OCP in frameworks?

HardAtlassian

Ask Aria about Open/Closed Principle

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…