The next leg of our quest to uncover the deeper driving forces behind SOLID principles brings us to the Open/Closed Principal (OCP). To refresh, I think it’s important that if we’re going to say (read the next part in a mouth-breathing, war-hammer-playing, mega-nerd voice) “your design violates TLA,” we had better back it up with some solid reasoning.
Most blog posts focus on the what and how. On this tip there are some great resources out there already. I’ve even blogged about a technique for achieving OCP with template method and chain-of-responsibility.
No matter; we’re here to focus on the why! Without further explanation, let’s take a look at…
The Open/Closed Principle
OCP, as introduced by Bertrand Meyer, states:
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
Once we’ve decided and are satisfied with the cohesive responsibilities of our class, we want that responsibility to remain stable. Stability is a big, big deal. It allows us to keep forward motion and focus on adding new and interesting behavior in other places in our system. “The Tortoise & The Hare” fable springs to mind. Proceeding methodically and consistently by investing in the design of our software usually pays dividends in terms of the system’s total cost of ownership.
Jeremy Miller does a great job of linking SRP with OCP in his recent MSDN article:
You’ll never reach true Open Closed nirvana, but you can inch much closer by rigorously following the related Single Responsibility Principle: a class should have one, and only one, reason to change. One of the easiest ways to write classes that never have to change is to write classes that only do one thing. This way, a class only needs to be modified if the exact thing that it does needs to change.
Behavior inevitably changes. We can inject “hooks” or extensibility points into our classes. This lets us keep our existing classes stable while extending their behavior. In a certain sense we could reword OCP as maintain stability in your designs by providing extension points. Sometimes we want to distribute a library and more-often-than-not our code is broken up into multiple assemblies or packages and shared between teams, customers, and other developers in general. Providing hooks that “open classes to extension” help deal with variation just as it aids internal evolution. This topic hints at the larger subject of API design.
OCP in the Dynamic Paradigm
It’s interesting to think about how these principles in a dynamic language like Ruby, considering you can just open any class anywhere or use various techniques such as the instance_variable_set method or alias to completely modify the behavior of an existing class. Michael Feathers points out, in a comment on Dean Wampler’s post about OCP in Ruby, that’s something we can’t do in static languages:
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.
Indeed there are many problems associated with monkey patching flexibility (or fixes). I ran into an issue with this on the testability of a rails plugin. I resorted to a monkey patch just to get the test working, but in a production scenario, I’m thinking I would have been better off with an adapter. Yes, it’s possible to stick to OCP even with dynamic languages. At least as far as I’m concerned.
We Should Care About OCP…
…because closing our classes allows us to focus on new functionality. It makes us more productive.
…because more productivity means less waste. Less waste means less cost. Less cost means more money. More money is the goal.
…because when we create extension points in our classes we provide flexibility for the consumers of our software.
…because when we create extension points in our classes for our own needs, we create extension points the future needs of future customers without succumbing to YAGNI.
…because it lets us extend the behavior of components outside of our control. Even if we do control dependencies, it’s a shorter feedback cycle to add novel functionality when said dependencies are open to extension. Shorter feedback cycles equate to more productivity and, well, you know.
Next up? Q: Does the Liskov Substitution Principle matter if we favor composition over inheritance? A: You can bet your bottom dollar it does…