Pattern drift

When I first reviewed Design Patterns, I recommended it be published as a loose-leaf notebook. I suggested that authors provide regular updates (this was before the Internet was readily available!). I anticipated frequent updates and many more additions. 23 patterns didnâ’t seem like nearly enough.

Fast forward to 2006. Design Patterns has been in print unchanged for 12 years. Although recognized as a landmark book, it needs refreshing. In fairness, the book was so popular that there was little motivation to do so. An update is supposedly in the works.

I give my students the original text, but they struggle to read it and find relevance (C++ and graphics examples are a stretch for most). I always point them to other sources, both online and in print, to fill in the gaps. Many have written their own take on specific patterns. That is a good thing. In 1998, in a C++ Report article, John Vlissides acknowledged that pattern definitions aren’t cast in stone.

It seems you can’t overemphasize that a pattern’s structure diagram (class diagram) is just an example, not a specification. It portrays the implementation we see most often. As such, the Structure diagram will probably have a lot in common with your own implementation, but differences are inevitable and actually desirable. At the very least you will rename the participants as appropriate for your domain. Vary the implementation trade-offs, and your implementation might start looking a lot different from the Structure diagram.

In Refactoring to Patterns, Josh Kerievsky quotes Vlissides and then after illustrating the original Composite pattern:

Gives his own take on a single-class implementation:

Hmm. A single concrete class that could support either leaf or composite behaviors. Now that’s a thought—but is it still recognizable as a composite pattern? Sure, but what is it that makes a composite a composite and not just another structuring mechanism? Is it just a fancy name for a “tree structuring mechanism” or is there something more?

Bob Martin, in Agile Software Development, presents another variation. He illustrates composite with a Shape interface which defines a single method-draw(). That interface is realized by classes that are either primitive shapes or composed of other shape objects.

In 1994 when Design Patterns was published, interfaces weren’t present in popular programming language. The authors relied on abstract class definitions instead. Since I spend my time with C# and Java, today I’d likely recast many GOF patterns using interfaces instead of abstract classes, especially when there isn’t any common meaningful behavior to inherit.

But Bob’s example illustrates another important design choice. He didn’t force-fit composite behavior into a common abstraction shared by both composite and leaf objects. He isn’t alone in making this tradeoff. Many of my students after struggling to define meaningful operations common to both, throw up their hands and grumble that the GOF authors made the wrong tradeoffs when they specified the composite pattern. This sentiment is echoed on a RICE webpage:

In Design Patterns, the abstract component AComponent is shown as having accessor methods for child AComponents. They are not shown here because it is debatable as to whether one wants the Client to fundamentally view the Component as a single component or as a collection of components. Design Patterns models all Components as collections while the above design models them all as single components. The exact nature of those accessor methods is also debatable.

Debating design tradeoffs is healthy. That’s why I give my students GOF undistilled and we discuss tradeoffs as they learn patterns. This helps them gain design confidence as they articulate their values and say what they like and don’t like about the patterns as presented. But sometimes I think they’d prefer a simple canonical pattern form they could just use without much thought. I often point them to the Data & Object Factory website which has a quick pocket guide discussion of each pattern. But the composite pattern sample implementation there defines an abstract Shape class with empty add() and remove() methods. And leaf classes implemented add or remove by writing a console message, “can’t add/draw a shape to an xxx” A toy solution if I ever saw one! Perhaps if I paid $79 to purchase their Design Patterns Framework I’d see a more realistic implementation.

This leads me to wonder: what makes a pattern useful, how much change can or should it undergo, and how much stewardship should there be over pattern drift, pattern evolution and pattern explanations? I don’t expect patterns to be fixed and unchangeable. They should wiggle around a bit. But I like thoughtful discussions and reasonable examples. I wish there was a community that maintained an active PatternPedia repository where authors would be encouraged to keep their patterns up to date and where there were useful teaching examples, thoughtful reviews, and summaries of both new and classic patterns. The Portland Pattern Repository is a springboard for patterns, but it doesn’t seem very active. The Hillside website is useful, but it just points to other sources. I have more in mind a cross between Wikipedia and Amazon with an edge and an active editor/convener whose job is to keep us informed of the latest breaking pattern news. Sure I can search the internet and books for patterns. But my search feels scattered. I never know when I’ll stumble across some arcane shift, a mangling of a pattern’s intent, or a good trend to follow. It’s too hit and miss.

Currently, most patterns are copyrighted by authors and are locked up in relatively static media books or conference proceedings or magazine articlesâ or static online versions of the same. There’s no central source, no common repository for a growing body of pattern wisdom gained from experience. So when pattern interpretations shift, as they invariably do, it is in a quirky ad hoc manner. I don’t mind Kerievsky’s compact interpretation of Composite. I just wish his version was accessible to those who didn’t buy his book, and that there was a place for open debate about the merits of this implementation choice that was readily linked to other Composite pattern interpretations. Is this asking too much? Isolated works make it difficult to change/refine/
invigorate patterns with in a larger community of users/developers/pattern authors. What would it take to create a patterns commons? I’d be interested in hearing your thoughts.

Alan Shalloway’s Hat Trick

Why does design seems so effortless in the hands of a master and why do beginners find design so difficult? Alan Shalloway‘s talk about Emergent Design at Software Development Best Practices last September demonstrated three ways to get to an identical good design: apply design patterns, use commonality-variability analysis, and practice test driven development techniques. The problem he used to illustrate his talk was fairly simple. Design classes to support monitoring microwave chips and cards by requesting their status over either a TCP/IP connection or via SMTP. Messages may optionally be encrypted with either 64 or 128 bit encryption. Cards queue information and send it out no more than every 10 minutes unless there is an error (then they send it immediately). Chips send results immediately.

Alan started by describing a tall, ugly hierarchy with intermediate abstract classes and 24 concrete subclasses for each hardware type, encryption mechanism, transmission protocol combination. This design is clearly bad because it contains redundant behaviors and a combinatorial explosion of leaf classes.

Next he applied the GOF authors’ advice of favoring composition over inheritance, to create a design that had three families of classes—one for each variation of hardware type, transmission protocol, and encryption. The specific hardware class plugged in the appropriate encryption and transmission protocol helper classes together to implement the appropriate behavior variations. Alan then showed how test-driven development starts with a simpler game plan. Instead of understanding all variations upfront, create a clean design which supports just one partial feature (or story) at a time. Refactor as you add more capabilities following a few good design principles. The idea with TDD is to let the design emerge, one story at a time. The first story Allan specified was to request the status of a chip with 64 bit encryption transmitted via TCP/IP. The next story added the flexibility to support different encryptions, the third transmission via SMTP, and so forth. He concluded his design demonstration by applying commonality-variability analysis to create the same end result. In a nutshell commonality-variability analysis involves identifying common abstractions and variations, relationships between them, assigning them responsibilities, and then linking them together. It differs fundamentally from TDD in that you upfront analyze variations before creating classes that are then configured to work together.

Voila! Identical designs following different approaches and a handful of design heuristics. Is this a realistic expectation outside of a canned talk? Can mere mortals, students new to designs, or developers faced with considerably larger more complicated design variations perform such clean design factorings in a real-world setting? I’m highly skeptical. I’ve seen so many different designs for a more complex problem I present to students of my design class that I’ve stopped believing I know every reasonable solution. I am constantly amazed by the sheer number of different acceptable design solutions students create. Sure, there are recurring patterns and themes among a range of acceptable design solutions. But I don’t expect identical solutions.

Alan’s demonstration that a good design can be achieved three different way—an amazing hat trick—really hit home the point that you needn’t always start by knowing everything upfront. But toss in a few more wrinkles—add a communications port, a mechanism to gain access to that port, define different card and chip types (with different reports), cards that report at different time intervals, cards and chips that can be programmed to report or have to be polled (or both)—and I expect design solutions to really diverge. One designer may embed an if-then-else decision into a method while another may factor out a variation into a family of classes. Some designers check a condition before asking a helper to perform an action, rather than find the polymorphic way to hide those details. Instead of having an “empty encrypter” class, designers may use a variable to represent bits of encryption, and only invoke the encrypter if the variable’s value isn’t zero. Even though I strive for squeaky clean polymorphism and to eliminate external checks, I often can’t convince students or some designers of the benefits of using null objects. To them, creating another (null) class seems like more work than it’s worth.

So while I don’t expect different designers to produce identical results, I firmly believe that abstractions can either be developed as you go or plotted out ahead of time. Alan advises designers to selectively use these approaches. Work out pattern-oriented solutions ahead of time when you know what variations are prevalent. When design variations aren’t so straightforward, start simply and add support for variations as you go. By using test driven development and rigorously refactoring before adding new functionality, you can keep an emerging design clean. Just don’t expect everyone’s design aesthetics to line up. Commonality-variability analysis seems to be a useful anlytic technique whenever I’m laying out the facts as I consider how to support a related set of variations.