Open/Closed Principle

Topics Covered

Open for Extension, Closed for Modification

Why This Matters

The Boundary Is Abstraction

When Code Violates OCP

Extending Behavior Without Changing Code

The Violation

The Fix

Interfaces Define Extension Points

The Ripple Effect

Strategy-Based Extension

From Switch Statement to Strategy

Runtime Flexibility

Combining Strategies

OCP in Real Systems

Middleware Pipelines

Plugin Architectures

Event Systems

Practical Limits

Every time you add a feature by editing existing code, you risk breaking something that already works. A function that handles three cases today works fine. Tomorrow you add a fourth case, and a subtle bug appears in the second case because your new else if shifted a boundary condition. The tests pass for the new feature, but the regression in the old feature goes unnoticed until production.

This is the problem the Open/Closed Principle (OCP) solves. Bertrand Meyer coined it in 1988, and the idea is deceptively simple: software entities should be open for extension but closed for modification.

"Closed for modification" means that once a module is working and tested, you should not need to change its source code to add new behavior. "Open for extension" means the module should be designed so that new behavior can be added by writing new code: not by editing existing code.

Adding a new shape type by creating a new class vs modifying existing code

Why This Matters

Consider a report generator that outputs PDF, HTML, and CSV. Without OCP, the generator has a method with three branches:

python
1def generate(self, data, format_type):
2    if format_type == "pdf":
3        # 40 lines of PDF logic
4    elif format_type == "html":
5        # 35 lines of HTML logic
6    elif format_type == "csv":
7        # 20 lines of CSV logic

When someone requests Excel support, you open this file, add an elif, and hope you do not break the indentation or logic of the existing branches. Every new format touches this method. Every touch is a risk.

With OCP, you define a ReportFormatter interface with a format(data) method. PDF, HTML, and CSV each become their own class. Adding Excel means creating a new ExcelFormatter class. The generator calls formatter.format(data) without knowing or caring which formatter it received. Zero changes to existing code.

The Boundary Is Abstraction

The key insight is that abstraction defines the boundary between open and closed. An interface or abstract base class is the contract. Everything above the contract (the code that uses it) is closed: it never changes. Everything below the contract (the implementations) is open: you add new ones freely.

This is not about making code impossible to change. It is about designing the system so that the most common changes (adding new types, new strategies, new behaviors) happen by addition rather than modification.

Key Insight

The Open/Closed Principle does not mean you never edit existing code. It means you design your system so that the changes you anticipate most frequently, new types, new strategies, new rules, can happen by writing new classes rather than modifying existing ones. The abstraction layer is the hinge that makes this possible.

When Code Violates OCP

You can spot OCP violations by looking for symptoms. If a single method grows a new branch every time a business requirement changes, it violates OCP. If adding a new payment type requires editing five files, it violates OCP. If a switch statement on a type field appears in multiple places across the codebase, each of those locations violates OCP: and each will need updating when a new type arrives.

The fix is always the same pattern: extract the varying behavior behind an interface, and let the calling code depend on the interface rather than the concrete implementations.