Design patterns that implement the Open/Closed Principle
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
Patterns commonly used for OCP include Strategy for interchangeable algorithms, Decorator for adding behavior around the same interface, Template Method for controlled subclass hooks, Factory Method for extensible creation, and Chain of Responsibility or registries for adding handlers.
For example, a request pipeline can hold a list of RequestHandler implementations. Each handler decides whether and how to process a request. A new audit handler can be registered without adding another branch to the pipeline. Java I/O decorators similarly wrap a Reader to add buffering while preserving the Reader contract.
The pattern must be implemented carefully. A factory containing a central switch still changes for every product, and an interface that changes for every new variant is not a stable extension point. Patterns offer structures for OCP; the actual test is whether the expected change stays localized and preserves existing contracts.
Design patterns that implement the Open/Closed Principle
Direct Answer
Strategy, Decorator, Template Method, Factory Method, Chain of Responsibility, and plugin or handler registries can support OCP by creating explicit variation points. No pattern guarantees OCP: a central factory switch or an unstable interface can still require modification for every new case.
Code Example
// Strategy pattern: swap sorting algorithms
interface SortStrategy {
void sort(List<Integer> numbers);
}
class QuickSort implements SortStrategy {
public void sort(List<Integer> numbers) {
Collections.sort(numbers); // Using built-in quick sort
}
}
class BubbleSort implements SortStrategy {
public void sort(List<Integer> numbers) {
// Bubble sort implementation
}
}
// Decorator pattern: add logging to any strategy
class LoggingSortDecorator implements SortStrategy {
private final SortStrategy wrapped;
public LoggingSortDecorator(SortStrategy wrapped) {
this.wrapped = wrapped;
}
public void sort(List<Integer> numbers) {
System.out.println("Sorting started with " + numbers.size() + " items");
wrapped.sort(numbers);
System.out.println("Sorting completed");
}
}
@Service
class DataProcessor {
private final SortStrategy sortStrategy;
public DataProcessor(SortStrategy sortStrategy) {
this.sortStrategy = sortStrategy;
}
public void processData(List<Integer> data) {
sortStrategy.sort(data); // Extends via strategy, decorates via wrapper
}
}
// Usage: new DataProcessor(new LoggingSortDecorator(new QuickSort()))
Common Mistakes & Pitfalls
- Listing patterns without explaining the extension point: name the stable contract and what new code can be added.
- Calling every factory extensible: a central product switch still changes for each new case.
- Confusing callback templates with subclass-only Template Method: explain the actual mechanism used by the library.
- Treating a security filter chain as Decorator by default: chains and decorators have different object relationships.
- Adding indirection without real variation: patterns have a maintenance cost too.
Check your understanding
Answer these without looking back at the article.
- What is the Strategy design pattern?
- How is Decorator different from inheritance?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.