Design patterns are the enemy of agility. They introduce repetition and accidental variation to your codebase. Design patterns encourage you to create "point solutions" throughout your application, instead of cleanly isolating concerns. And they will make your code refactor-proof, no matter how cool your IDE is. But there is hope: Catch your design patterns while they are young, and teach them to be library calls instead. Here's one example:
In Ruby, we often re-open existing classes and add instance methods. One approach is simply to open the class:
Or, you could create a new module and mix it in:
There are other approaches that are similar but not quite the same. In other words, this is a design pattern. From the Wikipedia entry:
A design pattern is not a finished design that can be transformed directly into code. It is a description or template for how to solve a problem that can be used in many different situations.
The problem with design patterns is the "not a finished design" part of the definition. Rather that a DRY solution, design patterns give you repetition throughout the code. Worse yet, the repetition is not exact. It is repetition with variation, and there is often no evidence whether the variation is intentional or accidental.
I like Ruby because I can eliminate design patterns when they start to annoy me. This "Open Class Add Method" pattern annoyed me for the last time earlier today, when two different libraries defined incompatible versions of Object#metaclass
. Enough is enough. Let's make a library call for reopening classes.
Here are my design goals:
These three goals are in conflict (and we could easily come up with more). This illustrates another problem with design patterns. Each time a design pattern is used, a programmer favors some design goals over others. Over time, this leads to a codebase at odds with itself. If the same pattern were captured in a reusable module, then changing design priorities could be handled from that module alone.
Here's a strawman proposal for cleanly adding methods to existing Ruby classes. The following code adds #jump
to Object
:
The syntax is simple and involves just one block, meeting goal one. Behind the scenes, I use __FILE__
and
__LINE__
to define a module, which gives us auditability (goal two):
Finally, the code that mixes in the module walks the inheritance hierarchy first, printing a warning whenever a name collision is encountered (goal three).
The complete implementation is included at the bottom of this post. I am sure it can be improved in several ways, but even in its primitive state it beats a design pattern. As long as the API is decent, we can always make the implementation suck less later.
Should I make a gem out of this? What changes would you like to see in the API? How should the handling of method collisions be specified?