Java Omnibus
OOAD & Architecture / Design principles / Open/Closed Principle in Java
Design Principles Java Order Management
Design Principles

The Open/Closed Principle matters when change is expected and the main business flow should stay stable.

OCP says software entities should be open for extension but closed for modification. That does not mean code should never change. It means the stable core should not need repeated rewrites for every predictable variation. In OOAD, OCP is about choosing where variation belongs.

Why This Topic Matters

Why it matters in real Java systems

Order-management systems regularly gain new discount types, payment methods, shipping carriers, and notification channels. If every new option requires rewriting the same service class, variation is not localized. OCP helps create extension points around that expected change.

Core Explanation

The design idea behind this topic

OCP is strongest when the business flow is stable but one part of the behavior varies. That usually points toward strategies, policies, plugin-style collaborators, or focused interfaces.

In Java, OCP often appears through dependency injection and polymorphism, but the real design question is earlier: what should vary independently from the core use case? If that question is answered well, the code structure follows naturally.

Bad OCP attempts produce speculative abstractions. Good OCP identifies actual, recurring variation and gives it a clean seam.

Concept Breakdown

Key ideas to hold onto

Concept

Stable core, variable edges

Keep the main workflow stable while pushing foreseeable variation into policies or implementations.

Concept

Extension points must be meaningful

An abstraction only helps if the variation is real and recurring.

Concept

OCP depends on good modeling

You cannot make the system open for the right extensions if the domain boundaries are unclear.

Java Example

Java example in the Order Management domain

Java example: extending discounts without rewriting checkout
package org.javaomnibus.ecommerce.principles;

import java.util.List;

public final class DiscountEngine {
    private final List discountRules;

    public DiscountEngine(List discountRules) {
        this.discountRules = List.copyOf(discountRules);
    }

    public Money apply(Order order, Money subtotal) {
        Money current = subtotal;
        for (DiscountRule discountRule : discountRules) {
            current = discountRule.apply(order, current);
        }
        return current;
    }
}

interface DiscountRule {
    Money apply(Order order, Money subtotal);
}

record Order(String orderId) {}
record Money(java.math.BigDecimal value, String currency) {}
Code Walkthrough

Step by step

  1. DiscountEngine stays stable while individual discount behaviors can be added as new DiscountRule implementations.
  2. The extension point is explicit and limited to the area that actually varies.
  3. The engine remains readable because its job is to orchestrate discount application, not to know every rule inline.
  4. This is OCP working at a useful seam rather than as a theoretical slogan.
Real-World Usage

Where this shows up in practice

  • Discount engines, notification routing, payment selection, and shipping policy decisions
  • Product lines where new business variants arrive regularly but the core flow remains stable
  • Plugin-like integrations in Spring or modular systems
Trade-Offs

When not to over-apply it

OCP can create unnecessary abstractions if the variation never materializes or stays trivial. Not every `if` statement is a design smell. The cost of extension should be lower than the cost of indirection you introduce.

Common Mistakes

Frequent mistakes to watch for

  • Abstracting before the variation is real
  • Creating a generic extension point that is too broad to reason about safely
  • Forgetting that extension still needs clear contracts and test coverage
  • Assuming OCP removes the need to revisit existing design choices
Related Pages

Related concepts


What To Read Next