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.