The Open-Closed Principle for Languages with Open Classes 14
We’ve been having a discussion inside Object Mentor World Design Headquarters about the meaning of the OCP for dynamic languages, like Ruby, with open classes.
For example, in Ruby it’s normal to define a class or module, e.g.,
# foo.rb
class Foo
def method1 *args
...
end
end
and later re-open the class and add (or redefine) methods,
# foo2.rb
class Foo
def method2 *args
...
end
end
Users of Foo see all the methods, as if Foo had one definition.
foo = Foo.new
foo.method1 :arg1, :arg2
foo.method2 :arg1, :arg2
Do open classes violate the Open-Closed Principle? Bertrand Meyer articulated OCP. Here is his definition1.
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
He elaborated on it here.
... This is the open-closed principle, which in my opinion is one of the central innovations of object technology: the ability to use a software component as it is, while retaining the possibility of adding to it later through inheritance. Unlike the records or structures of other approaches, a class of object technology is both closed and open: closed because we can start using it for other components (its clients); open because we can at any time add new properties without invalidating its existing clients.
So, if one client require’s only foo.rb and only uses method1, that client doesn’t care what foo2.rb does. However, if the client also require’s foo2.rb, perhaps indirectly through another require, problems will ensue unless the client is unaffected by what foo2.rb does. This looks a lot like the way “good” inheritance should behave.
So, the answer is no, we aren’t violating OCP, as long as we extend a re-opened class following the same rules we would use when inheriting from it.
If we use inheritance instead:
# foo.rb
class Foo
def method1 *args
...
end
end
...
class DerivedFoo < Foo
def method2 *args
...
end
end
...
foo = SubFoo.new # Instantiate different class...
foo.method1 :arg1, :arg2
foo.method2 :arg1, :arg2
One notable difference is that we have to instantiate a different class. This is an important difference. While you can often just use inheritance, and maybe you should prefer it, inheritance only works if you have full control over what types get instantiated and it’s easy to change which types you use. Of course, inheritance is also the best approach when you need all behavioral variants simulateneously, i.e., each variant in one or more objects.
Sometimes you want to affect the behavior of all instances transparently, without changing the types that are instantiated. A slightly better example, logging method calls, illustrates the point. Here we use the “famous” alias_method in Ruby.
# foo.rb
class Foo
def method1 *args
...
end
end
# logging_foo.rb
class Foo
alias_method :old_method1, :method1
def method1 *args
p "Inside method1(#{args.inspect})"
old_method1 *args
end
end
...
foo = Foo.new
foo.method1 :arg1, :arg2
Foo.method1 behaves like a subclass override, with extended behavior that still obeys the Liskov-Substitution Principle (LSP).
So, I think the OCP can be reworded slightly.
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for source modification.
We should not re-open the original source, but adding functionality through a separate source file is okay.
Actually, I prefer a slightly different wording.
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for source and contract modification.
The extra and contract is redundant with LSP. I don’t think this kind of redundancy is necessarily bad. ;) The contract is the set of behavioral expectations between the “entity” and its client(s). Just as it is bad to break the contract with inheritance, it is also bad to break it through open classes.
OCP and LSP together are our most important design principles for effective organization of similar vs. variant behaviors. Inheritance is one way we do this. Open classes provide another way. Aspects provide a third way and are subject to the same design issues.
1 Meyer, Bertrand (1988). Object-Oriented Software Construction. Prentice Hall. ISBN 0136290493.

I wouldn’t say it’s normal in Ruby to define a class in foo.rb and then re-open it in foo2.rb later. It’s certainly possible, but it’s more common to re-open someone else’s class, such as String. If you own the class, then the normal thing to do is just edit the source, or if you want to adhere to OCP, then sub-class or decorate.
I’m still intrigued by the design implication of open classes. I agree that opening classes does not necessarily violate OCP. But contrary to Dave Hoover’s comment, I frequently want to reopen my own classes to avoid violating the ISP, Interface Segregation Principle.
For example, I have class Dog. One client of Dog uses it to bark(). Another client uses Dog to bite(). The barking client doesn’t care about biting and the biting client doesn’t care about barking. So in each client I’ll open the Dog class and add the desired behavior.
But I wonder…. is this bad design? Reading the code becomes more challenging since the behavior of Dog is in many locations.
“Object Mentor World Design Headquarters.”
WOOOAAAAHHH!!!
@Micah,
That’s an interesting spin on class definitions and would indeed be confusing. If we weren’t talking Ruby, I would suggest defining two different interfaces for the Dog class that your clients can use and leave the two methods in the same class definition – ILoudDog and IAggressiveDog.
I am impressed, too, with “World Design Headquarters”.
This creates the impression that there are several design locations around the world. It also suggests that there are other non-design locations, maybe a training headquarters, a development headquarters, a QA headquarters, etc.
Would not such divisions of software construction go against the Agile philosophy?
Good post! =) I would like to see more posts about Object Oriented Design inside the dynamic languages world, mainly the Ruby world.
I have a couple of problems with seeing OCP as a source level thing. The first is that if we do, OCP doesn’t really offer much guidance. In a language as dynamic as Ruby, you can replace anything you like without touching the original source. If the source of a class can always remain closed (i.e., it doesn’t have to change) we could legitimately say that dynamic languages give you OCP trivially so it’s probably not worth talking about OCP at all.
The second problem I have with seeing OCP as a source level thing is that open classes present problems of their own that, ideally, OCP should say something about. OCP adherence often triggers the creation of orthogonal abstractions: you split the class and then you know that mucking with one responsibility isn’t going to muck with the other. With open classes, you could easily make a change to a class in one file which accidentally clobbers functionality in another file. In a behavioral sense, the original file was never really closed then, was it? Other clients could be affected.
Languages which allow self-modifying code are a bit of a different animal than what Meyer was considering.
Concerning World Design Headquarters, I was of course inspired by the Daily Show ;)
@Hugo, thanks. I’m sure we can add more blogs on “dynamic design”!
Here’s another perspective; what we really care about is feature modularity. I’d like to be able to reason about how a feature behaves (for any appropriate definition of “feature”). Think about OOP for a second; we allow the definition of a class to be spread over several “physical artifacts”, i.e., a base class + mixins/interfaces + subclasses, sometimes in separate files. If I want to reason about the behavior of a
FileInputStream, I have to look in several places, for example.I remember when OOP went viral. Lots of C programmers complained that they couldn’t figure out what their code was doing anymore, because the flow of control hoped around. With better tools (and drugs?) we got used to it.
The challenge of open classes is similar; we need modular reasoning over a set of artifacts. Tooling will help, so will programming conventions, practice, etc.
I agree to a point with Michael that I don’t think Meyer intended OCP to be just a source thing. It really is a statement of the power of extension through inheritance. He probably didn’t have open types in mind. (IIRC, Eiffel doesn’t support them.) That’s partly why I added the “contract” bit, so that our “non-local” extensions are done in a principled way.
One of the guiding principles of dynamic languages like Ruby is that there should be rules, but nearly always with escape hatches.
I think open classes (or at least some uses of them) are definitely violations of OCP, in the same way that #instance_variable_get violates encapsulation. That doesn’t mean it’s bad; it just means it’s worse than other alternatives.
Face it … it’s pretty hard to design a class that gets it exactly right with respect to OCP, and is extensible in all the right ways. And we have to work with classes that aren’t ideally designed. In the face of that, those escape hatches are invaluable.
The danger, of course, is that when designing classes we will just rely on the escape hatches and not try to design our classes for extension.
@Glenn I agree. I keep thinking that there’s a principle below all of this which is something like: Never write code that nullifies the behavior of existing code.
We should be able to understand the system from the sources. If we see a method, we should know that its behavior is there in the system, and although people may add to that behavior in various ways, they won’t nullify it through other code.
Ruby’s dynamic nature allows you to make the kind of changes you proposed (adding a method) without violating OCP. You can even modify the original source without violating OCP. Consider the basic Ruby class:
class Dog def bark; “woof!” end end
and its equivalent-ish Java class:
public class Dog { public String bark() { return “woof!”; } }
Now we add another method to Ruby.Dog:
class Dog def bark; “woof!” end def snarl; “gnashing of teeth” end end
and we go on with business as usual.
Now we add another method to Java.Dog:
public class Dog { public String bark() { return “woof!”; } public String snarl() { return “gnashing of teeth”; } }
and now we have to recompile every client of Dog.
To me, OCP is all about avoiding changes that screw up clients of a given class. You can really screw them up by changing the implementation in such a way that violates the original contract, or you can simply cause a nuisance by forcing them to be recompiled. In Ruby and other dynamic languages, you still have to think about the first part, but at least the second class of problems has gone away.
@PatMaddox: “and now we have to recompile every client of Dog.”
No we don’t. Try it.
@Glenn and @MichaelFeathers, agreed that “nullification” or “throw-a-curve-ball-ification” are no no’s.
One of the problems I’ve thought a lot about lately is how an “optimal” domain model is only optimal in a narrow context. In the large enterprises I work with, different subsystems and parts of the business need the same type to have different attributes and behaviors. It’s something of a lose-lose for the designer. If the type has the superset of stuff, that’s bad. If we have lots of variants of the type (with boilerplate copying back and forth), that’s bad for different reasons.
I want a way to keep a consistent, universal domain model, yet provide context-dependent extensions, even to shared instances. That and world peace….
By the way, the aspect-oriented programming community has wrestled a lot with ways to specify allowed extension points to a type without assuming anything (or much) about the actual extensions.
What happens if the class itself performs the extensions? For example, using some kind of rule (e.g. naming convention), it finds all of its extensions and “loads” them once and for all?
The class is closed – source not changing.
The opening up of the class is defined in one place (the class itself) but the extension of the class can be determined by the source code (to Feathers point).
I’ve got such an example I’m working with (a simple one sure).
About the only thing I’d want to add is that the extension point verifies that the other “openings” don’t step on each other.