Monday, November 10, 2008

Design Patterns - The Cult to Blame ?

I always thought GOF Design Patterns book achieved it's objective to make us better C++/Java programmers. It taught us how to design polymorphic class hierarchies alongside encouraging delegation over inheritance. In an object oriented language like Java or C++, which does not offer first class higher order functions or closures, the GOF patterns taught us how to implement patterns like Command, Strategy and State through properly encapsulated class structures that would decouple the theme from the context. As long as we do not link them with the pattern definition and theory as espoused by Christopher Alexander, the GOF book has an invaluable contribution towards today's mainstream OO community.

But that's only the good part. I was wondering what made many people cringe at the patterns movement that seem to deluge the programming community for well over a decade.

One of the drawbacks that the pattern movement in the programming community suffered from, was that, the design patterns as promoted by the GOF book, focused too much on the implementation aspects, the how part of the solution, instead of the what part of problem that they solve. This resulted in the implementation language being the main focus of the entire pattern. People started thinking that C++ is the language of choice since all of the GOF design patterns can be implemented faithfully using the honored constructs of the language. And, of course, in due time, Java, positioned as a better C++, also inherited the same honor. It may not be totally coincidental that during the time we were busy adulating the virtues of GOF design patterns in Java and C++, development of newer programming languages were at a very low ebb.

Developers started thinking that Java and C++ are the be-all and end-all of programming language implementations, since we can implement all GOF patterns in them. Enterprise software loves to follow common practices, rote techniques that can be easily replicated through a set of replacable programmers, and the combination of Java and design patterns made a perfect match. Switching languages was difficult, and even for some mainstream programmers today, switching to more powerful languages like Ruby or Scala, the initial thought processes were limited to the abstraction levels of Java. In reality, the patterns movement created a cult and made us somewhat myopic towards implementing higher order abstractions even in any other more powerful programming language.

Mark Jason Dominus reiterated this way back in 2006 ..

"If the Design Patterns movement had been popular in the 1980's, we wouldn't even have C++ or Java; we would still be implementing Object Oriented Classes in C with structs, and the argument would go that since programmers were forced to use C anyway, we should at least help them as much as possible."

Are Design Patterns useless ?

Absolutely not. Patterns are solutions to problems in context. As long as the problems remain, patterns remain equally relevant. What we need to do is highlight on the intent of the pattern, the problem that it solves, the forces that it resolves and the resultant forces that it generates, instead of just documenting how it solves it in one particular language.

As an example, the Strategy pattern is intended to decouple the implementation of an algorithm from the context of the application. In case of Java, it is implemented through the context class delegating the responsibility to the strategy interface, that can have multiple implementations. Looks and sounds ceremonious, but that is what it is, if you use Java as the implementation language. In languages that support higher order functions, the same pattern is implemented much more succinctly using native language features. Hence the implementation is subsumed into the natural idioms of the language itself. Again, if we highlight on the intent of the pattern, it remains *equally relevant*, irrespective of whether we use Ruby, Groovy, Scala or Lisp for implementation. And the intent is loud and clear - the implementation of the algorithm is a separate concern than the concrete context to which it is being plugged into.

Similarly, the Visitor pattern has often been maligned as an overtly complex structure that should be avoided at any cost. Visitor looks complex in Java or C++, because these languages do not support higher order abstractions like pattern matching or multiple dispatch mechanisms. Languages like Scala that support pattern matching have been known to implement succinct visitors without much ceremony. This paper, presented in OOPSLA 2008, implements the Visitor pattern as a reusable generic type-safe component using the powerful typesystem of Scala.

Irrespective of how you implement, Visitor remains a potent solution to the problem of separating the structure of abstraction hierarchies from the behavior of traversals over that hierarchy. The important part is to add the intent of Visitors to your design vocabulary - it is just the level of abstractions that the language offers that makes the implementation invisible, informal or formal.

Many people also talk about Singleton implementation in Java as being too elaborate, complex and unnecessarily verbose. On the other hand, singleton is available as a library call in Ruby and as a single word language syntax in Scala. That does not make Singleton a simpleton, it is just that Java does not offer a sufficiently higher level of abstraction to express the intent and solution of the pattern. Go, use dependency injection frameworks, they offer Singletons as configurable behaviors ready-to-be-wired with your native objects.

Patterns as a vehicle of communication

.. and not as code snippets that can be copy pasted. The real intrinsic value of design patterns is that they facilitate communication in processes by encouraging a common vocabulary and format that binds the design space. Coplien mentions in his monograph on Software Patterns ..

"Besides, we believe that human communication is the bottleneck in software development. If the pattern literary form can help programmers communicate with their clients, their customers, and with each other, they help fill a crucial need of contemporary software development.

Patterns are not a complete design method; they capture important practices of existing methods and practices uncodified by conventional methods. They are not a CASE tool. They focus more on the human activities of design than on transformations that can blindly be automated. They are not artificial intelligence; they celebrate and encourage the human intelligence that separates people from computers."


People, mostly from the dynamic language community, frown upon design patterns as being too ceremonious, claiming that most of them can be implemented in dynamically typed and functional languages much more easily. Very true, but that does not undermine the core value proposition of design patterns. Maybe most of us became converts to a cult that focused too much on the implementation details of the patterns in a specific family of languages. Ideally we should have been more careful towards documenting beautiful patterns and pattern languages as the core vocabulary of designers.

Some people on the Erlang mailing list recently talked about OTP being the embodiment of Erlang design patterns. OTP is clearly a framework, that helps develop highly concurrent client server applications in Erlang. Patrick Logan correctly points out that OTP is a collection of patterns, only in a very poor sense of the term. The real collection would be a pattern language that documents the problems, identifies the forces at play and suggests solutions through a catalog of literary forms that will help develop concurrent server applications from ground up. In case of Erlang, gen_server, gen_event, gen_fsm etc. collaborate towards implementing the pattern language. Similar efforts have been known to exist for Scala as well. Implementing the same in any other language may be difficult, but that will only point to the deficiencies of that particular language towards implementing a concrete instance of that pattern language.

19 comments:

Quintesse said...

I must say that even as a long time Java developer I never took the GOF book as a book on "how" to implement patterns. Maybe it was nice that the examples were given in a way that translated easily to the language that I use daily, but for me (and I know for many of my colleagues) the book was entirely about "ideas". And even more important a "common vocabulary". Because of course we had been using patterns for years but when the book came out we finally were able to talk about them without going into lengthy explanations.

Thierry said...

Very interesting article.
As a Swing java developper I always thought "design pattern" book was interesting but does not match very much with my usual day to day developper work.

I discover for example that ... well some of them can be integrated in a language with higher abstraction features.

This is nice (and new to me).


Very interesting article.
Thanks
Thierry

Alex Miller said...

You're speaking my language man. This is exactly the point and focus of my talk Design Patterns Reconsidered which you can find here:

Design Patterns Reconsidered

I think if the GoF had called the book "Design Problem Patterns" that might have changed the focus a bit on identifying common problems and then discussing the many possible solutions/variations of those problems. In some cases, the problem is addressed directly in a language feature. But the problem (say the "expression problem" defined by Wadler addressed by the visitor pattern) continues to exist and need a solution, whether it's in the language or not.

Luc Duponcheel said...

Patterns are often promoted as vehicles of communication.

A partially agree.

On the one hand not talking in terms of commonly agreed upon patterns may lead to babylonian misunderstandings (and have done so in the past, for example in meetings I've been in).

On the other hand, when just throwing patterns to other attendees of meetings (maybe just to show of (?)) they are communication barriers.

In short, I'm a pattern evangelist, not a pattern fundamentalist, let alone a pattern terrorist (oops, I used the word)

Luc

Debasish said...

@alex:
"But the problem (say the "expression problem" defined by Wadler addressed by the visitor pattern) continues to exist and need a solution, whether it's in the language or not."

That's precisely the problem that I have discussed. So long we have the "expression problem" to solve in real life situations, we will need Visitor. It is just true that Scala makes the implementation much succinct with pattern matching and extractors as part of the language. While it is a traditional implementation of GOF Visitor in languages like Java and C++.
Either way, the design pattern "visitor" does not become irrelevant - in some languages the implementation becomes informal or visible.

Debasish said...

@luc:
"On the other hand, when just throwing patterns to other attendees of meetings (maybe just to show of (?)) they are communication barriers"

That's why the description of patterns in literary form is of fundamental importance rather than documenting their implementations in any particular language. Coplien has explained it in real lucid terms in his book Software Patterns. And of course Alexander's A Pattern Language is the best example of such a catalog.

Krzysztof (Christopher) Kliś said...

I would be very interested in studying design patterns not bound to any specific language. Most design patterns just solve language specific problems, and in my opinion the number of books about design patterns for C++ and Java just show how deficient they are in many areas. Any idea for common problems in C++ and (suppose) Erlang to be solved by a common design pattern?

Debasish said...

@krzysztof:
Design patterns are NOT intended to solve language specific problems. Reading GOF book, you may have the feeling that most of the patterns would have disappeared if C++ had higher order functions. This is so, since, reading the text, we get more focused on the implementation aspects of the pattern in that specific language. In reality the pattern never goes away, since the problem never goes away. One example that I have mentioned is of the Visitor design pattern. It addresses a common problem of keeping traversals independent of the abstraction hierarchies. In Common Lisp you will implement the pattern using built-in multiple dispatch of CLOS. In Erlang or Scala, you will solve the problem using pattern matching. While you need to code explicit visitors to solve the same problem in Java or C++. This is an example where the problem is common across languages, the pattern tries to solve the problem, but does so with varied levels of abstraction depending on the language used.
Does this answer your question ?

Krzysztof (Christopher) Kliś said...

Debasish,
You wrote that "What we need to do is highlight on the intent of the pattern, the problem that it solves, the forces that it resolves and the resultant forces that it generates, instead of just documenting how it solves it in one particular language". In the context of your answer, you solve the problem differently in different languages, using the tools that a language provides. I am wondering how you can show a "generic solution" without resorting to examples specific for certain programming languages.

Krzysztof (Christopher) Kliś said...

Just to make myself clear. You can explain a concept of (let's say) quick sort without showing any specific implementation, using only abstracts, since you operate on data, which is (in most cases) independent from the language. Design patterns, as far as I understand them, solve problems that involve code (flow control, multiple method dispatching, etc). My question was about the notion apparatus that can be used to describe design patterns independently from their implementations, for example how to say about singletons in languages that do not support classes.

James Iry said...

Even quicksort changes its nature given different language semantics. A purely functional quicksort is a different beast from an imperative quicksort. Not just because of syntactic issues, but because imperative quicksort makes assumptions about the computational model like O(1) array random access and mutation.

I think Debashish is right in the utility of pointing out the relationship between Visitor, pattern matching, and mutltiple dispatch. But I'm with Krzysztof in that calling all of them the same "pattern" essentially takes the teeth out of the pattern concept.

Debasish said...

@krzysztof:
"My question was about the notion apparatus that can be used to describe design patterns independently from their implementations, for example how to say about singletons in languages that do not support classes."


Let me reiterate my positioning with the example of Visitor design pattern. Every design pattern solves a problem. Using naive design techniques, we often find that for a family of abstractions, the implementation of their representations and the operations get mixed up. As a result it becomes difficult to extend either of the concerns without impacting the other. This is the precise problem that Visitor tries to solve and is generalized as the Expresion Problem.

Now look at the intent of the pattern according to the GOF book, which is really the solution in generic terms and independent of any programming language :

“Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new
operation without changing the classes of the elements on which it operates.”


What it implies is that the operations on an abstraction need to have separate representations, so that they can be changed without affecting the structure of the abstractions.

This solution that it proposes is independent of the language of implementation.

Now when we consider the implementation aspects of it, we are entering into the abstraction levels offered by the various language. And the pattern becomes formal, informal or invisible depending on the blubness of the language.

So, my point is, so long as the Expression Problem remains a problem, the solution recommended by the Visitor Pattern is also very much relevant, and is independent of the lang of implementation.

And regarding singletons, we can consider them to be globals in disguise. In procedural and functional languages, we can use globals, which are nothing but singletons.

Debasish said...

@james: "But I'm with Krzysztof in that calling all of them the same "pattern" essentially takes the teeth out of the pattern concept."

But doesn't all of them solve the same general problem that Visitor addresses ? and hence position themselves as viable implementations of the pattern ?

James Iry said...

I hear you, really. And I'm even on the same page as far as trying to convey to software developers that there are overarching concepts like doing a fold over a data structure or separating algorithms into composable pieces that transcend language squabbles.

I just don't think the phrase "design pattern" is the right one to apply here. For instance, in Java I might very well use single dispatch and inheritance where in Haskell I would use an ADT and pattern matching. Yet they are clearly different things with different strengths and weaknesses (cf expression problem). It would be hard to call them both the same design pattern even though they're both about the same generalized problem of dispatch. In Java I rarely create visitors to solve that kind of problem - visitors are such a pain in arse in typical OO languages that most OO programmers don't bother most of the time.

Debasish said...

@james:
"It would be hard to call them both the same design pattern even though they're both about the same generalized problem of dispatch."


I think you are considering only the implementation as the whole design pattern. In reality, according to Alexander, the major important part of a pattern is the intent and the forces that it balances. The implementation is only the solution part, which, again, can vary depending on the abstraction level of the language used. Here all the solutions, be it single dispatch based of Java or ADT and pattern matching based one of Haskell, address the same problem, the Expression Problem and balances the same set of forces in the context. Being different implementations, they give rise to a different set of resultant forces, which need to be balanced by other patterns. In case of the Java based solution, the resultant forces are too difficult to balance - adding of operations become easier (which addresses the main problem), but the data structures become too closed for changes (violation of OCP). Hence people in the Javanese land tend to avoid visitors, while the Haskellers and Scalanese people get along merrily with friendly visitors.
So, ADT with pattern matching or the single dispatch based with inheritance is NOT the design pattern in its entirety - the pattern is the entire triplet of [problem, context, solution].

James Iry said...

I just can't get on board. In Scala if I want a visitor I write a visitor that looks pretty much like it does in Java. In Haskell if I want a visitor I pass a bunch of functions to a dispatcher. In either case I get pretty much, more or less, what I would expect from a Java visitor : a ton of boilerplate, but a clean separation of representation from dispatch*.

But usually I don't want a visitor in the later two languages - I use pattern matching instead. Then I get exhaustiveness checking, nested patterns, etc.

* ignoring GHC 6.10 views and Scala extractors, which are another topic

Debasish said...

@James:
"But usually I don't want a visitor in the later two languages - I use pattern matching instead."


That's correct! The implementation of the pattern in the 2 languages (Scala and Haskell) are more succinct, informal and almost invisible. You don't have to code an explicit Visitor. The language feature (pattern matching) takes care of that, and you only have to supply the dispatch routes.

"Then I get exhaustiveness checking, nested patterns, etc."

And the bonus that u get, like exhaustiveness checking etc. serve to balance some additional forces that u do not get with the hand-coded visitor implementation in Java. Possibly u will need to write more boilerplates in Java to address these forces.

Anonymous said...

If you take an example of Strategy Pattern, no doubt, it separates algorithm from it's user so that the a new algorithms can be added with little code changes.. but this doesn't eliminate need to identify all the strategies and the conditions on which they are selected at compile time..So what big of a use this pattern is? I can solve the same separation problem in multiple other ways! Why should I use this patter at all? pardon my ignorance

Debasish said...

"but this doesn't eliminate need to identify all the strategies and the conditions on which they are selected at compile time" ..
Strategy is a runtime pattern. Only the interface needs to be defined during compile time. U can have new implementations plugged in dynamically if u use Dependency Injection or any of the dynamic factory patterns.