Why not to study design patterns
I sometimes get asked why I don't teach design patterns. After all, they contain little nuggets of design wisdom, and isn't it helpful to have names for common things.
Both of those are true. But I counter: advances in understanding often come with having fewer names.
In my web course, I make a similar attack on refactoring catalogues, devoting a lecture to showing how many refactorings emerge as applications of a few basic building blocks like reverse substitution. "You'll leave this lecture knowing less than when you entered," I say. Here's my take on design patterns.
What is a design pattern?
There are lots of design patterns. The Factory pattern is something that lets you dynamically pick a class and looks like a certain UML diagram. The Singleton pattern lets you make sure there's only one of something and looks like a certain UML diagram. The Builder pattern lets you invoke a complicated constructor more flexibly, and also looks like a certain UML diagram.
There seems to be a pattern to these patterns. And, indeed, the original 1993 design patterns paper defines a design pattern as containing:
So, by their original definition, design patterns are specifically an object-oriented thing. Looking at some modern catalogues of design patterns, that seems to still be the common use. A lot of other things could be called patterns, like the RAII idiom in C++, or even the idea of an object itself, but they are not design patterns by this definition.
- An abstract description of a class or object collaboration and its structure. (i.e.: a UML diagram)
- The issue in system design addressed by the abstract structure, which determines the circumstances in which the design pattern is applicable.
- The consequences of applying the abstract structure to a system's architecture.
But wait! That's not exactly fair. You can also read about functional programming patterns, and SQL patterns, and Linux kernel patterns, and probably, somewhere, toaster-programming patterns.
Well, if you broaden the definition to "every repeatable solution to a family of problems," which is not that far off from the original definition of patterns in architecture and town planning, then I can't say much about them other than that's they're repeatable and they solve problems, which would make for a very short newsletter. Nonetheless, in software engineering, "design pattern" still tends to mean "design expressed as object-collaboration diagram," and in particular AbstractSingletonFactory and friends. I did a Google search just now for "design patterns" in quotes. 18/19 results on the first few pages were for object-oriented design patterns, with UI Design Patterns appearing near the bottom of the second page. OO design patterns were similarly dominant in DuckDuckGo, except that I also learned a lot about how to choose a new couch. I'll be just referring to "object-oriented design patterns" for the rest of this newsletter.
The way of the Haversine
Advances in understanding often come with having fewer names. Let's look outside software for examples.
I learned some math history in college, and this reduction in terminology is very clear in the evolution of math notation over the past millennium.
Do you know what the cube of a number is? How about the zenzizenzizenzic? (The zenzizenzizenzic of x is the eighth power of x, x8.)
Or how about: Do you know what the sine and cosine of an angle is? How about the versine and haversine? (The versine is 1 minus the cosine; the haversine is half of the versine.)
These terms all come from the days before modern mathematical notation and calculators. Back then, there were tables of different variations of trigonometric functions for use in navigation, and the only way to discuss equations was to describe them in paragraph form, making words such as “zenzizenzizenzic” useful.
For an example of “paragraph equations,” here’s Fibonacci of Pisa describing in his famous 1202 AD book Liber Abaci how to solve an equation of the form x2=bx+c.
And when it will end in the solution of some problem that the census is equal to a number of roots plus a number, then you add the square of half the number of roots to the number; and to the root of that which will result you add half the number of roots, and you will have the root of the sought census.
He then gives a separate (and longer) description for how to solve x2+c=bx.
Today, we would write 1-cos(Θ) instead of versine(Θ), x8 instead of the zenzizenzizenzic of x, and teach only a single formula to solve quadratic equations of all forms. We no longer need words to describe all the parts of a quadratic formula.
I believe object-oriented design patterns and refactoring catalogues are analogous to these antiquated words, and, indeed can mostly be seen as instances of more general language features, or even of specific type signatures in functional programming. And I'm not alone. In fact, for a long time, I considered writing a blog post on the topic, but then I discovered Peter Norvig had beaten me to it by 20-something years. We do not give names to most type signatures because we can just give the type signatures. If many object-oriented design patterns are really just type signatures, then these patterns do not need names either. When the entire catalogue has been replaced by more basic concepts, OO design patterns will go the way of the haversine.
I’ve found myself on rare occasions using the names of some patterns and refactorings when they were complex and I didn’t have a better way to describe them quickly, but the number of such cases is shrinking over time. I used to use the term “conditional to subclass refactoring,” described by “father of refactoring” William Opdyke in his 1992 thesis. But I will never use it again, because I now know this is just a special case of refunctionalization.
So why do people like design patterns?
Nonetheless, a lot of people really like OO design patterns.
The knee-jerk snooty academic response is to say that that's because OO design patterns are industrial snake oil peddled to unenlightened programmers.
But, actually, OO design patterns originated in academia, and their advocates include plenty of brilliant people, including my own undergraduate advisor, Jonathan Aldrich. So let's take a harder look.
What are the reasons to use them? In broad strokes, OO design patterns provide a common vocabulary, reduce complexity by naming abstractions, and provide a reusable base of experience for building software, acting in essence as "micro-architecture." The original paper lists a few more benefits, but I see the others as consequences or variations of the three named.
The vocabulary argument is easily illustrated (source). The junior carpenter explains his plan: "We should make this joint in these drawers by cutting straight down into the wood, and then cut back up 45 degrees, and then going straight back down, and then..." The senior simply says "We can use a dovetail joint or a miter joint." So it goes with software engineers, where a lot of talk about passing around an object whose class overrides some method can be condensed into "strategy pattern."
On the other hand: What do wooden joints have to do with object-oriented design patterns? A functional-programming advocate can use the exact same example and conclude "That's why you should learn to recognize things as maps and folds."
So, really, the carpentry dialogue is an example in favor of having terminology, not specifically in favor of doing so by names for abstract descriptions of class or object collaborations and their structure. To the claimed benefits, I say: yes design patterns give you those, and other things give you them better.
How can I make this broad claim about an entire way of presenting designs? I hinted above when I mentioned the idea that objects are themselves a pattern.
I regretfully have to tantalize you on what I mean by this, as it currently takes me 4-6 weeks to fully explain "true objects" in my group coaching program, after having previously given up trying to teach it in a 1-hour lecture. If I am to summarize in a few sentences, I'll say that (1) probably everything you know about objects is wrong, and (2) they can instead be regarded as a certain style of using existential types, which involves putting many representations behind the same interface and having them interoperate. To tantalize you a little less on what this all means, I'll point you to one of my favorite computer science essays ever, by the recently-diseased William Cook.
So if you accept this idea that objects themselves are a pattern and not fundamental, then it follows that object-oriented design patterns are not fundamental either. The Strategy pattern is more easily explained in functional programming because it's using objects and inheritance as a way of wrapping first-order functions. The Adapter pattern is a function between interfaces, except that this function is written as a constructor. The Flyweight pattern, which is really just another name for interning/hash-consing, has nothing to do with objects, and need not be presented as such.
So are there any patterns where objects are the most natural way to express them?
It turns out that "true inheritance" is not that easy to explain either, but, to take another brief stab: a use of inheritance is not "true inheritance" (inexpressible as composition/delegation) unless it involves both a subclass calling methods of the superclass and vice-versa. And the prototypical example of a good use of true inheritance is the visitor pattern.
So there is an example where the object-oriented way is simplest, and I can cheerfully say that I won't teach any object-oriented design patterns except the visitor pattern.
Though, I must say, the functional version of the visitor pattern, updatable fold algebras, lets you do some pretty cool stuff. Like deal with large bananas.
Thanks to Jonathan Aldrich for feedback on an earlier draft of this newsletter.