One of the biggest lies we tell starting developers is that design patterns are language independent. Whilst true at a high level, the truth is that a programmer in a modern programming language can junk most of the Gang of Four book. A couple of days ago, it was twenty years old. It’s time to celebrate its lasting positive influences, and then bury it.
Some things are potentially useful as terminology for discussing with people, but others aren’t even useful as that. The really obvious example is the template pattern: if you’re programming in a language that can use functions as values it’s utterly meaningless. Another is iterator: most programming languages have a list/sequence implementation and you just use that.
Prototype, equally is meaningless for two, entirely opposite, reasons: first, the whole concept originates in C++ where you can perform a raw memory copy. In a language such as Java that doesn’t have one it’s so cumbersome you’ll prefer a factory method. In a language such as F# or Clojure, ubiquitous persistence data structures mean that everything’s a prototype.
Command is basically a pattern that replaces functions with objects. In a functional programming language, this is just the normal way you do things. In languages such as Python and Clojure where objects can act as functions the line is further blurred. But that’s nothing compared to what you can do with Clojure’s multimethods.
Multimethods and Protocols
Quite a few patterns are just workarounds for the painfully restricted dispatch patterns in old OO languages. The visitor and adapter patterns are both ways of circumventing the closed nature of classes in C++/Java. When you can just associate new methods with existing data structures, even third party code, you just don’t need them.
Also, if you understand multimethods for more than just class based dispatch, you see that it subsumes the state pattern.
(defmulti state-pattern (fn [tool data] tool)) (defmethod state-pattern pen-tool [tool data] nil)
How about a strategy pattern?
(defmulti strategy-pattern (fn determine-strategy [tool data] ...)) (defmethod state-pattern :strategy1 [tool data] nil)
In practice, you can use multimethods to mix and match dispatch on raw parameter value (state), dispatch on computed value (strategy) and dispatch on class (visitor). Similar effects can be achieved using Haskell’s type features.
Then there’s stuff that’s just a special case of something more general. Chain of responsibilty in Clojure is easily implemented using the
(defn chain-of-responsibility ([elements] (partial chain-of-responsibility elements)) ([elements data] (some #(% data) elements)))
Is chain of responsibility really useful terminology here, or is it just “using the
Then there’s ones that are just plain outdated: observer and mediator are rarely a better choice than a decent pub/sub mechanism. Heck, even your language’s event system is often a better choice. And I think everyone’s got the message about singleton by now.
I’m concerned this will be seen as down on the whole concept of patterns. Actually, high level patterns, the kind that Martin Fowler talks about are fine and last a long time. But our understanding of patterns constantly evolves (see pub/sub) and the ergonomics of specific patterns varies wildly between languages. GoF was a great book, and made a huge positive impact, but it’s time to take it off our shelves.
One thought on “Design Patterns: Happy Birthday and Goodbye”
Adapters are an interesting case. They're literally a way of working around the type system. Specifically, that you can't make an object someone else created expose one of your interfaces. This makes it vital in languages such as Java or C# where the type system isn't that flexible, but unnecessary in languages like Clojure or Haskell where that restriction doesn't apply. (Languages like Python and Ruby are a bit of a grey area, in that the restriction doesn't apply, but violating the restriction is often a really bad idea.)Equally, I'm not saying "don't use factories and strategies" but "realize they're special cases of much more general parameterization approaches".I find your maintainability point interesting. The "this class implements a pattern" is a special case of the single responsibility principle. The problem is that anyone can argue their class has one responsibility (if sometimes by doing a bit of violence to language) and I agree that you don't have that problem if you can say "this class should be this pattern".In a functional language, I'm tempted to say that SRP applies to functions, not classes.