Introduction to design patterns, Part 2: Gang-of-four classics revisited

Learn the Gang of Four design patterns in Java code, starting with Strategy and Visitor

1 2 Page 2
Page 2 of 2
Asteroid hit a SpaceShip
Asteroid hit an ApolloSpacecraft

ExplodingAsteroid hit a SpaceShip
ExplodingAsteroid hit an ApolloSpacecraft

ExplodingAsteroid hit a SpaceShip
ExplodingAsteroid hit an ApolloSpacecraft

Asteroid hit a SpaceShip
ExplodingAsteroid hit a SpaceShip

Asteroid hit an ApolloSpacecraft
ExplodingAsteroid hit an ApolloSpacecraft

Now that we've fully unpacked the double dispatch technique, let's return to the Visitor pattern.

Using Visitor to print and evaluate abstract syntax trees

Design Patterns uses a compiler context as the Visitor pattern's motivation. Although Visitor can be used in any hierarchical context where you don't want to mix operations on a given structure with the structure's elements, we'll follow the GoF example to study this pattern.

A compiler converts source code into executable code. The analysis phase scans characters into tokens, parses the tokens to verify syntactic correctness, and creates an abstract syntax tree (AST) that represents the program as a hierarchy of nodes.

The AST is then type-checked to verify semantic correctness (variables must be declared before they are used, for example). This data structure is also commonly used to drive code generation. These operations are often performed by visitors.

Consider an expression language involving addition, subtraction, multiplication, division, negation, floating-point numbers, and parentheses. An expression AST features nodes representing all of these items except for parentheses, which determine the tree structure.

AST visitors visit these nodes and are based on a class hierarchy that's rooted in an abstract class or interface. The root type abstracts methods for visiting the different kinds of nodes, and is extended by concrete subclasses that implement specific kinds of visitors, as shown in Figure 1.

Diagram of the Visitor pattern Jeff Friesen

Figure 1. The Visitor pattern is partly based on a hierarchy of visitor classes

Figure 1 reveals a convention in which each visitor method begins with the word visit and ends with the name of the node class. Furthermore, the argument passed to this method has the node class as its type.

Each concrete AST class declares an accept(Visitor) method that receives a visitor class instance as its argument. This method invokes the appropriate visitor method on this argument, and passes a reference to itself as the invoked method's argument, as illustrated by Figure 2.

Second diagram of the Visitor pattern Jeff Friesen

Figure 2. The Visitor pattern is partly based on a client-specific hierarchy of node classes

Figure 2 describes an AST for the expression: 4.0+2.0*3.0. For brevity, only the code executed by NumNode's accept(Visitor) method is shown. The getName() method is used to return the name of the node, and is only invoked by the print visitor.

Evaluating expressions via visitors

I've created a downloadable expression evaluator application that creates an AST for the expression language. The following excerpt from the application's EE class parses an expression (identified by args[0]) into an AST:

Parser p = new Parser();
ASTNode n = p.parse(args[0]);

The created AST is then visited to print its node hierarchy and evaluate its expression. For example, the excerpt below creates a print visitor and passes it to the accept(Visitor) method of the AST's root node:

n.accept(new PrintVisitor());

The application simulates double dispatch in Java code as follows:

  1. Single dispatch causes the accept(Visitor) method of the appropriate ASTNode subclass instance (whose reference is stored in n) to be invoked with the PrintVisitor instance as its argument. (ASTNode is implemented as Java interface, but it could just as easily have been implemented as an abstract class.)
  2. Within accept(Visitor), v.visitx(this); is executed, where x depends on the accept(Visitor) method that is called. Single dispatch causes v.visitx(x); in the appropriate Visitor subclass instance (whose reference is stored in v) to be invoked with the current node's reference as its argument.

The various visitor methods continue to traverse the tree by making accept(Visitor) calls on child nodes. For example, the following PrintVisitor class excerpt shows how PrintVisitor's visitAddNode(AddNode n) method visits its left and right child nodes:

public void visitAddNode(AddNode n)
{
   depth++;
   n.left.accept(this);
   depth--;
   println(n.getName());
   depth++;
   n.right.accept(this);
   depth--;
}

Running the expression evaluator

The following command shows you how to use EE to evaluate the expression: 4.0+2.0*3.0:

java EE 4.0+2.0*3.0

When you run this command, you should observe the following output:

4.0
+
        2.0
    *
        3.0

4.0+2.0*3.0 = 10.000000

 

Design Patterns tips

Now that we've looked in detail at two of the GoF patterns, let's turn our attention to the book that started it all. This section and the next will be a guide for readers new to the Design Patterns book. Reading the design patterns critique in the next section may be especially helpful for newer developers.

Design Patterns is essentially a catalog of 23 software design patterns. Despite presenting a short tutorial on each pattern along with a larger case study, the book is largely laid out as a reference manual. Following a simple study plan will help you get the most out of it.

If you're new to design patterns, I recommend that you start by reading the introduction in Chapter 1. You'll learn what constitutes a design pattern, discover how design patterns are described, find out how to select and use design patterns, and more. You might want to read this chapter a couple of times to let its concepts sink in. Next, read Chapter 2, which introduces several design patterns in a document editor case study.

Chapters 3, 4, and 5 comprise the catalog of patterns. Each chapter begins with an overview of the type of pattern being discussed (creational, structural, or behavioral) and then presents the patterns in alphabetical order. You probably shouldn't attempt to read through these chapters sequentially because you'll quickly get bogged down and start forgetting important details.

Instead, try studying a single chapter to learn how to distinguish between patterns of a similar type. Alternatively, you might browse from pattern to pattern. I believe that an inexperienced object-oriented designer would be well-off first studying the following eight design patterns, which are some of the simplest and most commonly used of the 23 patterns in the book. (This advice is from the readers guide that precedes Chapter 1 of Design Patterns, by the way.)

  1. Abstract factory
  2. Adapter
  3. Composite
  4. Decorator
  5. Factory method
  6. Observer
  7. Strategy
  8. Template method

Another option is to follow the "simple followed by intermediate followed by advanced" pattern order proposed by Web developer Michael Mahemoff in "GoF Design Patterns: Rapid Learning Tips." Finally, for the Java developer looking for Java-based examples, I recommend David Geary's Java design patterns series on JavaWorld. Geary doesn't cover all 23 patterns, so you might want to also check out James Sugrue's DZone series, Design patterns uncovered.

Regardless of your approach to exploring the 23 patterns, consider summarizing and implementing each pattern (as I demonstrated with the Strategy pattern earlier in this article) for yourself. That will help you understand and retain the pattern, and will make the pattern your own, rather than letting it remain the product of someone else's work.

Design Patterns critiqued

The GoF's Design Patterns book has been praised by many, but it hasn't stood the test of time without some critique. One well-known critic is Mark Jason Dominus, who famously criticized GoF software design patterns in his "Design Patterns Aren't" slideshow presentation.

Dominus points out that pattern languages as introduced in Christopher Alexander's Pattern Language book (the architectural inspiration for software design patterns) help you decide what needs to be designed without telling you how to design it. You make up the patterns that you think will lead to good designs. In contrast, he sees the GoF's approach as discovering existing patterns and then implementing them habitually.

A participant in a StackOverflow topic addressing sources of design pattern criticism reinforced that viewpoint:

The feeling is that when we teach patterns we cause developers, especially junior developers, to try to cram all problems into the set of patterns that they have learned, which can create more obtuse and cumbersome problems.

Software developer Jeff Atwood has also critiqued the GoF patterns in a blog post entitled "Rethinking Design Patterns." Atwood says that the GoF patterns rely too heavily on boilerplate code: "[I]t's a sign that your language is fundamentally broken [when] you find yourself frequently writing a bunch of boilerplate design pattern code to deal with a 'recurring design problem,'" he writes. Atwood also finds design patterns as a whole overly complex, stating that developers should focus on simpler solutions before applying complex design pattern recipes.

Conclusion to Part 2

Software design patterns are part of a larger pattern universe. The final article in this series will broaden the usual discussion of design patterns by focusing on patterns relevant to software development, but not directly related to code. We'll look at interaction design patterns, architectural patterns, and communication and presentation patterns and discuss their relevance to contemporary software development.

Learn more about design patterns in software development

David Geary's Java design patterns series on JavaWorld: Learn more of the Gang of Four patterns in Java code:

This story, "Introduction to design patterns, Part 2: Gang-of-four classics revisited" was originally published by JavaWorld.

Copyright © 2012 IDG Communications, Inc.

1 2 Page 2
Page 2 of 2