Design patterns across paradigms by Mark Seemann
This blog post makes the banal observation that design patterns tend to be intimately coupled to the paradigm in which they belong.
It seems as though lately it has become hip to dismiss design patterns as a bit of a crutch to overcome limitations in Object-Oriented Design (OOD). One example originates from The Joy of Clojure:
"In this section, we'll attempt to dissuade you from viewing Clojure features as design patterns [...] and instead as an inherent nameless quality.
"[...]the patterns described [in Design Patterns] are aimed at patching deficiencies in popular object-oriented programming languages. This practical view of design patterns isn't directly relevant to Clojure, because in many ways the patterns are ever-present and are first-class citizens of the language itself." (page 303)
From what little I've understood about Clojure, that seems like a reasonable assertion.
Another example is a tweet by Neal Swinnerton:
and Stefan Tilkov elaborates:
First of all: I completely agree. In fact, I find these statements rather uncontroversial. The only reason I'm writing this is because I see this sentiment popping up more and more (but perhaps I'm just suffering from the Baader-Meinhof phenomenon).
Patterns for OOD #
Many 'traditional' design patterns describe how to solve problems which are complex when working with object-oriented languages. A lot of those problems are inherent in the object-oriented paradigm.
Other paradigms may provide inherent solutions to those problems. As an example, Functional Programming (FP) includes the central concept of Function Composition. Functions can be composed according to signature. In OOD terminology you can say that the signature of a function defines its type.
Most of the classic design patterns are based upon the idea of programming to an interface instead of a concrete class. In OOD, it's necessary to point this out as a piece of explicit advice because the default in OOD is to program against a concrete class.
That's not the case in FP because functions can be composed as long as their signatures are compatible. Loose coupling is, so to speak, baked into the paradigm.
Thus, it's true that many of the OOD patterns are irrelevant in an FP context. That doesn't mean that FP doesn't need patterns.
Patterns for FP #
So if FP (or Clojure, for that matter) inherently address many of the shortcomings of OOD that give rise to patterns, does it mean that design patterns are redundant in FP?
Hardly. This is reminiscent of the situation of a couple of years ago when Ruby on Rails developers were pointing fingers at everyone else because they had superior productivity, but now when they are being tasked with maintaining complex system, they are learning the hard way that Active Record is an anti-pattern. Duh.
FP has shortcomings as well, and patterns will emerge to address them. While FP has been around for a long time, it hasn't been as heavily used (and thus subjected to analysis) as OOD, so the patterns may not yet have been formulated yet, but if FP gains traction (and I believe it will), patterns will emerge. However, they will be different patterns.
Once we have an extensive body of patterns for FP, we'll be able to state the equivalent of the introductory assertion:
Most of the established FP patterns address shortcomings of FP. Using the FloopyDoopy paradigm makes most of them redundant.
What would be shortcomings of FP? I don't know them all, but here's a couple of suggestions:
Apart from calculation-intensive software, most software is actually all about mutating state: Take an order. Save to the database. Write a file. Send an email. Render a view. Print a document. Write to the console. Send a message...
In FP they've come up with this clever concept of monads to 'work around' the problem of mutating state. Yes, monads are very clever, but if they feel foreign in OOD it's because they're not required. Mutation is an inherent part of the OOD paradigm, and it very intuitively maps to the 'real world', where mutation happens all the time. Monads are cool, but not particularly intuitive.
FP practitioners may not realize this, but a monad is a design pattern invented to address a shortcoming in 'pure' functional languages.
It means that given a variable, we can often just enter ".", and our IDE is going to give us a list of methods we can call on the object:
Since behavior is contained by each type, we can use patterns such as Fluent Interface to make it easy to learn a new API. While we can laugh at the term 'dot-driven development' it's hard to deny that it makes it very easy to learn.
The API itself carries the information about how to use use it, and what is possible. That's very Clean Code. Out-of-band documentation isn't required.
I wouldn't even know how to address this shortcoming in FP, but I'm sure patterns will evolve.
Different paradigms require different patterns #
All of this boils down to a totally banal observation:
Most patterns address shortcomings specific to a paradigm
Saying that most of the OOD patterns are redundant in FP is like saying that you don't need oven mittens to pour a glass of water.
There might be a slight overlap, but I'd expect most patterns to be tightly coupled to the paradigm in which they were originally stated.
There might be a few patterns that are generally applicable.
The bottom line is that FP isn't inherently better just because many of the OOD design patterns are redundant. Neither is OOD inherently better. They are different. That's all.