raganwald
(This is a snapshot of my old weblog. New posts and selected republished essays can be found at raganwald.com.)

Sunday, January 06, 2008
  Another problem with problems with design patterns


The documentation for a design pattern describes the context in which the pattern is used, the forces within the context that the pattern seeks to resolve, and the suggested solution. [For example:] The visitor design pattern is a way of separating an algorithm from an object structure. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures.

I have read the view that design patterns represent deficiencies in a programming language. This argument stems from the observation that the specific code examples for half or more of the patterns in the GoF Book are "superfluous" in languages that provide abstractions above and beyond basic OO. The Visitor pattern can be said (depending on which Golden Hammer you are holding) to represent a way to brew your own multiple dispatch, open classes, or pattern matching.

Fools ignore complexity. Pragmatists suffer it. Some can avoid it. Geniuses remove it.

Does this mean that given a sufficiently expressive language there will be no design patterns? Certainly not. Each design pattern organizes a problem and one suggested solution in a structured way, giving the solution a name so that programmers may enjoy a common vocabulary. The pattern would only go away if the problem goes away.

But why would a more expressive language have this better feature, if not to solve the problem? Clearly the problem doesn’t go away, it is just that instead of writing our own incomplete and buggy mechanisms for multiple dispatch we can now use the language’s complete, tested, and “free” multiple dispatch feature to solve the problem.

If you consider the problem to be “How do I implement my own multiple dispatch,” then the Visitor pattern is obsolete if you are using a language like Common Lisp. However, if you consider the problem to be “How do I separate algorithms from data structures,” then the only thing obsolete in the originally publicized pattern are the language-specific details. The high-level approach can still be relevant.

A programming language is low level when its programs require attention to the irrelevant.

Some programming languages accomplish the same task with a declaration or a singe line of code. From this, some draw the conclusion that design patterns exist because popular programming languages lack certain abstractions or they lack the meta-abstractions permitting you to build your own abstractions cleanly. The underlying assumption here is that the purpose of the pattern is to explain specifically how to write the code.

This is clearly not the case. The Ruby language implements the Singleton pattern with a single abstraction, the Singleton module. include Singleton is all you need to implement the pattern. Yet, would we say that in Ruby there are no Singletons? No, we would say that Ruby makes them easy.

I always felt the beauty of Java was that there is only one ‘standard’ way of handling most problems. I know it might not be ‘fun’, ‘smart’ or ‘elegant’ in most people’s views, but most software factories don’t want people to write elegant code. They want standardized maintainable stuff that people can understand ten years from now because it’s written in the same fashion [as] everything else out there…

I really think that great code is easy to understand and follows patterns that people are used to and comfortable with… I read someone’s code I just want to be able to understand it as quickly as possible, find the spot where the changes go and be able to figure out how said changes should fit in the general design.
—inopia commenting on Newly Discovered Design Pattern: “Code Well.” Reposted with permission.

One argument is that implementing code a certain way is what makes it easy to read, what makes it familiar. This line of thinking is grounded in the idea that the valuable part of the patterns is the suggested solution at a detailed level, because that is what will enable people to read the code and say to themselves, “I know what that does.”

The counter-argument is that designing architecture a certain way with familiar names is what makes it easy to understand. This line of thinking is grounded in the idea that the valuable part of the patterns is the identification of the context, forces, and resolution, because that is what will enable people to read the code and say to themselves, “I know what that means.”





A Pattern Language: Towns, Buildings, Construction is one of the most loved books on my shelf. Anyone who has an interest in making their house or apartment a deeply satisfying, functional home needs to read this book once in their lives. If you don’t have the budget to buy it today, I urge you to borrow it from a friend or from your library.

And of course, you can learn a great deal about software design from it. In many ways, you can learn much more about how to think about design than from software patterns books. One thing that stands out is that this is a Timeless Way of Building. When you see how these patterns transcend specific construction materials and methods, you the common forces in software design that transcend tools.
Even if the exact implementation of a pattern is abbreviated or even trivial in another language, patterns still provide the identification of the context, forces, and resolution with a common name everyone can understand.

A programming language can make some patterns go away. But not by providing features and abstractions that eliminate the repetitive boilerplate. Such languages make the patterns succinct, elegant, and remarkably easy to read… but not superfluous.

Instead, a language can make the patterns go away by introducing entirely different contexts for designing programs. To stretch the building metaphor, patterns of home design go away if you are designing a commune instead of separate family dwellings. It is not a question of how one builds homes, we do away with homes altogether.

Radically different paradigms for programming can eliminate the patterns we know. For example, if you are building a Common Lisp program with objects and use multiple dispatch, you are still thinking in objects and thus you wrestle with the same forces upon them.

But if you build a Common Lisp program in a functional way, you may be able to get by without any objects at all, not even objects you have faked using closures. In that case, you don’t need the OO patterns because you aren’t in an OO context.

But that being said, there are other patterns you need that suit a functional programming context like Iterator and Idiomatic Traversal. It is not a case of Common Lisp being so powerful that no patterns are needed, it is a case of Common Lisp providing the power to make certain patterns elegant and simple should you choose to program in a manner that can make use of them.

So languages can make patterns go away by introducing entirely new paradigms. And those paradigms will have their own patterns appropriate for resolving their forces.

In summary, I dispute that design patterns represent language flaws. I claim that while the specific suggested implementations may work around language flaws, the context, forces, and resolution are relevant and valuable as long as you are working within the same paradigm.

Get into a rut early: Do the same process the same way. Accumulate idioms. Standardize. The only difference(!) between Shakespeare and you was the size of his idiom list — not the size of his vocabulary.




All quotes from Wikipedia or from Alan J. Perlis’ Epigrams in Programming unless otherwise attributed.

post scriptum:

I notice that the first couple of comments that appeared on a popular link aggregation site disagreed with my perspective that design patterns are much more than the language-specific implementation. To them, a Singleton in Ruby is a library call, not a pattern.

Patterns have four elements. The argument seems to be that three of the four elements—name, context, forces to be resolved—belong to a “Concept,” and only the fourth—the one we don’t like—is a pattern.

Upon reflection, what I find most interesting about this perspective is how much it has in common with the pattern abuse that I have criticized in the past: an obsession with the surface form. I wrote about that above:

This line of thinking is grounded in the idea that the valuable part of the patterns is the suggested solution at a detailed level.

This criticism of patterns is rooted in exactly the same view of patterns: that he most important thing is the exact suggested solution, not the name, not the approach, not the context, not the forces to resolve. One person holds that up as a pattern’s value, another criticizes it, but they are much more similar than different in their views.
 

Comments on “Another problem with problems with design patterns:
Java's design is that of force minimalism, keeping the available abstractions to the minimum, and demanding that any pattern be expressed fully in code. Repeatedly. Ad nauseam.

Ruby, Lisp and any language that favors meta-programming takes a different approach. Identify a repeating pattern and create a suitable abstraction.

Take singleton for example. Using a singleton is a design decision. In Ruby, it's a feature offered by the language that you can add to any given class by including Singleton.

In Java it's a pattern of statements that you have to write in a particular order to implement that feature.

Defining a static method with a particular name (say getInstance) that synchronizes before accessing an instance variable to get/set the only instance, created using a private constructor is a pattern. It's repeating and you always do it the exact same way.

"including Singleton" is adding a feature to a class.
 
Assaf:

What you say is certainly a popular perspective. And interestingly, many peopel feel that what you said is a strength of Java, not a weakness.

You said "using a singleton is a design decision." I agree, and I am saying that Singleton the pattern is more than the boilerplate. Whether you use boilerplate, add a feature to a class, or use another technique involving an object and its eigenclass... it's still a useful concept.

I'm not sure whether we are in agreement or whether you are claiming that the pattern is the repeated code, not the Singleton concept.

If you are claiming the latter, we do not agree: I emphasize the architectural content of patterns, not the implementation suggested for one particular language by one particular author (or four). What Java makes you do is hardly the most important part of any design pattern.
 
Part of the problem is that, for a fair number of the patterns in GoF, there's no good abstraction for the implementation other than boilerplate code in Java and C++. The pattern language may still be interesting, but in trying to get out of the academic ghetto, the authors chose poorly expressive languages for those concepts.
 
Chromatic: why do you say that the Design Patterns authors were trying to escape the "academic ghetto"? Only one of them was (is) an purebred academic, one an academic/industrial researcher, and the other two industry practitioners.

C++ was the hottest new language in industry, designed and implemented by an industry practitioner, working in industry, at the time.
 
Reg: I think that you're heading in the right direction with this.

There are two ways to use patterns—learn them, and learn from them.

Let's consider learning them. Singleton is a nice example. It's become perhaps the most vilified of the GoF patterns (perhaps even more so than Visitor) because it is understood to be a global variable in disguise. And we "all know" that global variables are bad, bad, bad.

But that's not quite what the Intent of the pattern says, it says "Ensure a class only has one instance, and provide a global point of access to it". The Motivation section looks pretty ropy these days—why on Earth should there only be one print spooler?—and the Consequences section is very weak, because it only lists benefits.

It's interesting that the Known Uses section talks about Smalltalk metaclasses, and that's a clue: it wouldn't make any sense for there to be many metaclasses for a given class. So maybe we should really be using Singleton to model situations where we have a concept that only sensibly admits a single example (rather than just choosing to implement one that way).

Ah, but what if there were multiple instances? Ok, well what if there were but we couldn't tell? That would mean that those instances were value objects, and that would save us from one of the downside of Singleton that the Consequences (wrongly) don't mention: a global "variable" is a fairly harmless thing if it doesn't vary. This is an example of learning from patterns, and it's what people who "use" patterns don't do enough of.

What's been lost is that a pattern isn't by itself a solution to anything: there is a solution inside the pattern, but the whole pattern is a mapping between a less agreeable context and a more agreeable one.

I'm reminded of the difference between openings in chess and joseki in Go. Joseki are explicitly there to be learned from, not learned. Although, if you aren't familiar with a joseki that an opponent begins you will come off the worse for it—but you won't lose the game (well, not in the slap-to-the-face way that not knowing a chess opening can lead to, anyway). Joseki is that they don't lead to a definite win/lose scenario, they lead to a balanced outcome (say, white robs black of the corner but lives very small, and black gains great influence into the center but has to make life). What using a joseki does is get the corner out of the way so that the game can proceed to more interesting things.

And now I get to to my actual point: why is what I've said up there not in GoF? It's because the book is more than ten years old and in the 36th printing of it's first edition, as I write this. The subsequent thirteen years of discussion and development of patterns is missing from it. That's textbook territory, in all the most negative senses of the word. There's a desperate need for a second edition, and the surviving authors know it.

A friend of mine who's in the education business got into trouble a while ago while speaking at a college of education and said to them—re the latest teacher training methods—"there's nothing like thirty-year-old theory backed up by twenty-five-year-old practice.

The practice described in GoF is well on the way to being twenty years old. That'd doesn't mean that it can't be learned from, it emphatically can, but perhaps shouldn't be learned.
 
So, would you consider object-oriented class to be a pattern? How about "subroutine", referened later in that article?

Dominus quotes Norvig as mentioning three types of patterns - informal, formal and invisible. Perhaps most people think of the informal patterns when they think of design patterns - and it is those that represent deficiencies in a programming language. Certainly, the lack of a formal "object-oriented class" pattern in C could be thought of as a deficiency (albeit one that serves the higher purpose of keeping the language small), and the lack of a formal (or invisible) subroutine pattern is definitely a deficiency in any procedural language (so don't throw Haskell at me as a counterexample).
 
Perhaps if they'd called the book "Design Clichés", people might have seen it for what it was...
 
I agree with Reg's point, and I've already expressed my view on the issue Yes, design patterns are
Patterns are "common solutions to recurring design problems". The GoF book covers common OO design solutions. One expect that other paradigms (like functional) had other design patterns (if they have design at all). Maybe one can venture that a paradigm without patterns probably doesn't have a community mature enough to identify common solutions to recurring problems.
 
"And interestingly, many peopel feel that what you said is a strength of Java, not a weakness."

Count me in that category. That's one of the first things I loved about Java, and I still think an important part of the language. My post about C2EE was tongue-in-cheek, but the main point is: I'm in favor of sticking to Java's design choices, not looking back, not trying to change them. Some people view them as a weakness, but enough people view them as a strength, and I do think Java needs to play to its strengths.


"I'm not sure whether we are in agreement or whether you are claiming that the pattern is the repeated code, not the Singleton concept."

Forget that design patterns ever existed, forget that you ever knew Java or C, and try to write a book about all the same use cases, imagining everyone is programming in Ruby. Start with a blank slate, what would that book look like?

Would it have a section talking about object singletons, or will it talk about lazy-evaluated global variables? Would it talk about observers, or have a section covering list comprehension? Would it show an example for observers, or for including and extending Enumerable? Would it talk about listeners or passing lambdas?

If you go through this mental exercise, it will become more apparent that some of these design patterns are deeply rooted in Java/C and are not global software idioms. They would never have existed, or taken this particular form, if it wasn't for the popularity of Java/C. Starting from a different language, most would be delegated to features. New patterns would certainly emerge, at higher level of abstractions, to deal with shortcomings of the existing language. I can think of a few meta-programming patterns for Ruby that work around the lack of macros. But obviously those would only come around to cover restrictions imposed by the language.
 
Saying that a pattern is a pathology tied to the language/methodology it spawned from is true because it rapidly approaches a tautology. Any language you use is going to have its benefits and limitations - what's easy in C may be difficult in LISP and vice-versa. By swapping one language for another you're trading one (or more) class of accidental difficulties for one (or more) class of accidental difficulties, but the essential difficulty of software development will remain.
Design patterns work inasmuch as they're shorthand for gaining some insight and sharing vocabulary for problems that will repeat themselves in the language/methodology of your choice. They don't work when they're treated as canon that must be hammered in or you must be doing it wrong.
When you think about the reasons why design patterns work, you'll hopefully walk away with at least a little more isnight as to why your methodology works (and where it doesn't). I suspect we're vehemently agreeing on this, mind you.
 




<< Home
Reg Braithwaite


Recent Writing
Homoiconic Technical Writing / raganwald.posterous.com

Books
What I‘ve Learned From Failure / Kestrels, Quirky Birds, and Hopeless Egocentricity

Share
rewrite_rails / andand / unfold.rb / string_to_proc.rb / dsl_and_let.rb / comprehension.rb / lazy_lists.rb

Beauty
IS-STRICTLY-EQUIVALENT-TO-A / Spaghetti-Western Coding / Golf is a good program spoiled / Programming conventions as signals / Not all functions should be object methods

The Not So Big Software Design / Writing programs for people to read / Why Why Functional Programming Matters Matters / But Y would I want to do a thing like this?

Work
The single most important thing you must do to improve your programming career / The Naïve Approach to Hiring People / No Disrespect / Take control of your interview / Three tips for getting a job through a recruiter / My favourite interview question

Management
Exception Handling in Software Development / What if powerful languages and idioms only work for small teams? / Bricks / Which theory fits the evidence? / Still failing, still learning / What I’ve learned from failure

Notation
The unary ampersand in Ruby / (1..100).inject(&:+) / The challenge of teaching yourself a programming language / The significance of the meta-circular interpreter / Block-Structured Javascript / Haskell, Ruby and Infinity / Closures and Higher-Order Functions

Opinion
Why Apple is more expensive than Amazon / Why we are the biggest obstacles to our own growth / Is software the documentation of business process mistakes? / We have lost control of the apparatus / What I’ve Learned From Sales I, II, III

Whimsey
The Narcissism of Small Code Differences / Billy Martin’s Technique for Managing his Manager / Three stories about The Tao / Programming Language Stories / Why You Need a Degree to Work For BigCo

History
06/04 / 07/04 / 08/04 / 09/04 / 10/04 / 11/04 / 12/04 / 01/05 / 02/05 / 03/05 / 04/05 / 06/05 / 07/05 / 08/05 / 09/05 / 10/05 / 11/05 / 01/06 / 02/06 / 03/06 / 04/06 / 05/06 / 06/06 / 07/06 / 08/06 / 09/06 / 10/06 / 11/06 / 12/06 / 01/07 / 02/07 / 03/07 / 04/07 / 05/07 / 06/07 / 07/07 / 08/07 / 09/07 / 10/07 / 11/07 / 12/07 / 01/08 / 02/08 / 03/08 / 04/08 / 05/08 / 06/08 / 07/08 /