Loading learning content...
The year is 1988. Bertrand Meyer, a French computer scientist, publishes Object-Oriented Software Construction—a seminal work that would shape how an entire generation of programmers thinks about software design. In this book, Meyer articulates a principle that would eventually become one of the five fundamental tenets of object-oriented design: the Open/Closed Principle.
Understanding Meyer's original formulation isn't just historical curiosity. It reveals the foundational thinking behind OCP, explains why inheritance was once considered the primary extension mechanism, and clarifies how and why the principle has evolved. This context helps you apply OCP wisely—knowing not just the what but the why behind the principle.
By the end of this page, you will understand Bertrand Meyer's original vision for OCP, the central role inheritance played in his formulation, the historical context that shaped his thinking, and how this foundation evolved into the modern understanding we use today.
In Object-Oriented Software Construction, Meyer presents the Open/Closed Principle with careful precision. Let's examine his exact words:
"A software module is said to be open if it is still available for extension."
Meyer explains that a module is open if you can add new operations to it, extend its data structures, or augment its behavior. The module hasn't reached its final form—there's room for growth.
"A software module is said to be closed if it is available for use by other modules. This assumes that the module has been given a well-defined, stable description (the interface in the sense of information hiding)."
Closure means the module is ready for clients to depend upon. Its interface is stable. Other modules can use it with confidence that its behavior won't change in breaking ways.
Meyer then poses the central challenge:
"How can we satisfy both of these goals? The traditional approach to addressing this problem is, of course, that one cannot: a module should be in either state. Either we keep the module open for future extensions, or it is closed—hence committed to implementation, and available for the rest of the system to use."
This is the tension Meyer identified: in traditional (non-object-oriented) programming, opening a module for extension meant changing its source code, which also meant it wasn't truly closed anymore. Adding features required modifying existing modules, destabilizing everything that depended on them.
Meyer's breakthrough insight was that object-oriented programming—specifically inheritance—could resolve this tension:
"With inheritance, we can achieve both: a class can be closed, because it is compiled, verified, and used by other modules; at the same time, it is open, because any new class may use it as a parent, adding new features."
This is the core of Meyer's OCP: use inheritance to extend behavior without modifying the original class.
In Meyer's vision, inheritance is the primary tool for achieving OCP. Here's how it works:
The Base Class is Closed
Once you've designed, implemented, tested, and deployed a class, it becomes closed. Its source code is fixed. Other parts of the system depend on its behavior. You don't touch it.
New Subclasses Provide Extension
When new requirements arise, you create a subclass that inherits from the closed base class. The subclass can:
The Original Class Remains Unchanged
The key is that the base class source code never changes. It stays closed. Extension happens in the new subclass, which is a separate compilation unit.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
// MEYER'S APPROACH: Inheritance-Based OCP // The CLOSED class - compiled, tested, deployed// This code will never change once releasedclass Shape { protected x: number; protected y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } move(deltaX: number, deltaY: number): void { this.x += deltaX; this.y += deltaY; } // Original implementation - might just be a placeholder draw(): void { console.log(`Drawing shape at (${this.x}, ${this.y})`); }} // EXTENSION via inheritance - new file, new class// The Shape class is not modifiedclass Circle extends Shape { private radius: number; constructor(x: number, y: number, radius: number) { super(x, y); this.radius = radius; } // Override to provide specific behavior draw(): void { console.log(`Drawing circle at (${this.x}, ${this.y}) with radius ${this.radius}`); } // Add new capability getArea(): number { return Math.PI * this.radius * this.radius; }} // ANOTHER EXTENSION - added months later// Still no changes to Shape or Circleclass Rectangle extends Shape { private width: number; private height: number; constructor(x: number, y: number, width: number, height: number) { super(x, y); this.width = width; this.height = height; } draw(): void { console.log(`Drawing rectangle at (${this.x}, ${this.y}): ${this.width}x${this.height}`); } getArea(): number { return this.width * this.height; }} // Client code can work with any Shapefunction renderShapes(shapes: Shape[]): void { for (const shape of shapes) { shape.draw(); // Polymorphically calls the right draw() }}In this example:
Shape is closed: once released, its source code doesn't changeShape is open: new shapes (Circle, Rectangle) can be added by inheriting from itShapeThis is exactly what Meyer envisioned: the power of inheritance to achieve both openness and closure simultaneously.
To understand Meyer's emphasis on inheritance, we need to understand the programming landscape of the late 1980s:
The Rise of Object-Oriented Programming
OOP was gaining momentum as a paradigm shift from procedural programming. Languages like Simula (1960s), Smalltalk (1972), and C++ (1983) had introduced the concepts, but they were not yet mainstream. Meyer's book was evangelizing OOP as a superior approach.
Inheritance Was Revolutionary
In procedural languages like C or Pascal, extending functionality typically meant:
Inheritance offered something radically different: you could reuse and extend code without touching the original. This was genuinely novel and powerful.
| Aspect | Pre-OOP (1988) | Meyer's OOP Solution | Modern Approaches |
|---|---|---|---|
| Extending behavior | Modify source code directly | Create subclass that overrides | Implement interface, use composition |
| Adding new types | Edit switch/case statements | Add new subclass | Add new interface implementation |
| Reusing code | Copy-paste, macros | Inherit and specialize | Compose, delegate, mix-ins |
| Polymorphism | Function pointers, unions | Virtual methods | Interfaces, generics, protocols |
| Avoiding modification | Very difficult | Inheritance makes it easy | Many techniques available |
Interfaces Were Less Prominent
While the concept of abstract interfaces existed, they weren't as first-class or widely used as they are today:
interface keyword) wouldn't arrive until 1995Inheritance was the most accessible and powerful abstraction mechanism available. It's natural that Meyer built his principle around it.
Meyer's Language: Eiffel
Meyer designed his own programming language, Eiffel, which deeply reflected his philosophy. Eiffel featured:
OCP in Meyer's mind was inseparable from Eiffel's inheritance model. His examples and thinking naturally emphasized what Eiffel did best.
Every design principle is shaped by the technology and practices of its time. Meyer's OCP reflected 1988 OOP capabilities. Understanding this context helps you adapt the principle to modern tools—interfaces, generics, composition, dependency injection—rather than applying it rigidly.
Let's distill the essential elements of Meyer's original OCP formulation:
Meyer's Design by Contract integration
Unique to Meyer's formulation was the tight integration with Design by Contract (DbC). In his view, a class isn't just closed because its code is stable—it's closed because its contract is stable:
When you inherit and override a method, Meyer's rules require:
This contract discipline ensures that subclasses are true extensions that don't violate client expectations—foreshadowing what we now call the Liskov Substitution Principle (LSP).
Meyer's OCP wasn't just about structure (inheritance) but also about behavior (contracts). This combination ensures that extensions are safe and predictable. Modern OCP thinking sometimes loses this behavioral dimension, focusing only on code organization.
While Meyer's inheritance-based OCP was groundbreaking, experience revealed significant limitations:
1. Fragile Base Class Problem
When you inherit from a class, you're tightly coupled to its implementation—not just its interface. If the base class implementation changes (even in supposedly private ways), subclasses can break unexpectedly.
1234567891011121314151617181920212223242526272829303132333435363738394041424344
// FRAGILE BASE CLASS PROBLEM // Original base classclass Collection { protected items: string[] = []; add(item: string): void { this.items.push(item); } addAll(items: string[]): void { for (const item of items) { this.add(item); // Calls add() for each item } }} // Subclass that counts additionsclass CountingCollection extends Collection { private count = 0; add(item: string): void { this.count++; // Count each addition super.add(item); } getCount(): number { return this.count; }} // Usageconst cc = new CountingCollection();cc.addAll(["a", "b", "c"]);console.log(cc.getCount()); // 3 - correct! // BUT what if the base class is "optimized"?// If someone changes Collection.addAll to NOT call add():// addAll(items: string[]): void {// this.items.push(...items); // Bypass add() for efficiency// }// // Now CountingCollection breaks! addAll no longer increments count.// The subclass assumed an implementation detail of the base class.2. Deep Inheritance Hierarchies
Pure inheritance-based extension can lead to deep, rigid hierarchies:
Shape
└── TwoDimensionalShape
└── Polygon
└── Quadrilateral
└── Rectangle
└── Square
These hierarchies become:
3. Single Inheritance Limitation
Many languages (Java, C#, TypeScript) only support single implementation inheritance. If you've already inherited from one class, you can't inherit from another. This severely limits the "extension by inheritance" approach.
4. The "Gorilla-Banana" Problem
As Joe Armstrong (creator of Erlang) famously said:
"The problem with object-oriented languages is they've got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle."
When you inherit, you get everything from the base class—including things you don't want or need. This couples you to potentially unwanted behavior.
These problems don't invalidate OCP—they invalidate exclusive reliance on inheritance as the extension mechanism. The principle (extend rather than modify) remains valid; the implementation technique has evolved.
Recognizing these limitations, the interpretation of OCP evolved. Robert C. Martin (Uncle Bob) refined the principle in the 1990s and 2000s, shifting the emphasis from inheritance specifically to abstraction more generally.
Martin's key refinements:
Interfaces over Inheritance: Use interfaces (pure abstractions) as the primary extension point. Concrete implementations of interfaces provide the extension.
Composition as Extension: Combine objects through composition rather than extending them through inheritance. New behavior comes from composing new objects, not inheriting from existing ones.
Design Patterns as Templates: Patterns like Strategy, Template Method, and Decorator provide structured ways to achieve OCP without deep inheritance.
Abstraction Is the Key: The focus shifted to any abstraction that enables polymorphism: interfaces, abstract classes, generics, protocols, or even functions (in functional programming).
| Aspect | Meyer's Version (1988) | Martin's Version (1990s+) |
|---|---|---|
| Primary mechanism | Implementation inheritance | Interfaces and composition |
| Extension via | Subclassing base classes | Implementing abstractions |
| Key language feature | Virtual methods, overriding | Interfaces, polymorphism broadly |
| Hierarchies | Often deep inheritance trees | Flat interface hierarchies |
| Code reuse | Inherit and override | Delegate to composed objects |
| Flexibility | Limited by single inheritance | Unlimited interface implementation |
| Modern patterns | Template Method | Strategy, Dependency Injection |
The core insight remains
Despite the evolution in technique, the core insight of Meyer's OCP remains unchanged:
Martin's refinement broadened the "how" without changing the "why." Whether you use inheritance, interfaces, composition, or any other mechanism, the goal is the same: design systems that can grow through addition rather than surgery.
When applying OCP today, focus on the principle's goal (extension over modification) rather than any specific technique. Sometimes inheritance is appropriate. Other times interfaces, composition, or even functional approaches work better. The mechanism should fit the context.
We've explored the origins of the Open/Closed Principle as formulated by Bertrand Meyer. Let's consolidate the key insights:
What's next:
Now that we understand OCP's origins, we'll examine why modification is inherently risky. The next page explores the hidden costs of changing working code—regression bugs, testing burden, deployment complexity, and the accumulation of technical debt—providing concrete motivation for the extension-first approach OCP advocates.
You now understand Bertrand Meyer's original formulation of OCP, the historical context that shaped it, and how the principle has evolved beyond pure inheritance. Next, we'll examine the concrete risks of modification that make OCP so valuable.