程式扎記: [ In Action ] Working with builders - Learning by example via using a builder


2014年6月2日 星期一


As software developers, everything we do day in and day out is building: We build graphical applications, command-line tools, data stores, and a lot of other, often invisible products. To this end, we make use of components and frameworks as building blocks assembled on a fundamental base. We build by following the rules of the architecture and the best practices of our trade.

Not surprisingly, the general task of building faces us with recurring activities and structures. Over time, developer experience has led to proven standard solutions for repetitive building tasks captured in terms of patterns. One such pattern is the Builder pattern. In this pattern, a builder object is used to help build a complex object, called the productIt encapsulates the logic of how to assemble the product from given pieces.

Products can be complex because they maintain a tricky internal state (think of a parser object) or because they are built out of numerous objects with interdependencies. The latter case is frequently seen when there are tree-like structures that you find everywhere in the world of software.

Surprisingly, most programming languages have a hard time modeling this oh-so-common structure, especially building a tree-like structure in program code. Most of the time, the programmer is left with the task of calling several addChild and setParent methods. This has two major drawbacks:
* The logic of how to properly build the tree structure is often subject to massive duplication.
* When reading the code, it is hard to get an overall picture of the nesting structure.

To overcome the latter drawback, many approaches store the nesting structure in some external format, typically XML, and construct runtime objects from there. This, of course, has other limitations: You lose all the merits of your programming language when defining the structure. This leads to a lack of flexibility and is likely to produce a lot of duplication in the XML.

Groovy offers an alternative approach. Its builder support allows you to define nested, tree-like structures in the code, being descriptive and flexible at the same time. When you view the code, at least in reasonably simple situations, the resulting hierarchy is easily visible on the screen. Groovy enables this as a language through the use of closures, the Meta-Object Protocol (MOP), and simple map declarations. The library support comes from BuilderSupport and its subclasses including NodeBuilder ,MarkupBuilder , AntBuilder , and SwingBuilder.

Learning by example—using a builder:
Builders are easier to understand with concrete examples, so we’ll take a brief look at some sample code and compare it with how we’d achieve the same result without builders. At this point, we’re not going to present the details of builders, just the feeling of using them. We happen to use MarkupBuilder , but the general principle is the same for all of the builders.

Builders provide a convenient way to build hierarchical data models. They don’t allow you to create anything you couldn’t have created before, but the convenience they add is enormous, giving a direct correlation between hierarchy in the code and the hierarchy of the generated data. We demonstrate this by building the short XML document shown in listing 8.1. The XML contains information about the numbers 10 through 15, their square values, and their factors—for every number x the factors ysuch that x % y == 0 . Obviously, this isn’t a terribly useful document in real-world terms, but it means we can focus on the code for generating the XML instead of code required to gather more interesting data.
- Listing 8.1 XML example data: squares and factors of 10 through 15
  1. <?xml version="1.0"?>  
  2. <numbers>  
  3.   <description>Squares and factors of 10..15</description>   
  4.   <number value="10" square="100">  
  5.     <factor value="2" />  
  6.     <factor value="5" />  
  7.   </number>  
  8.   <number value="11" square="121" />  
  9.   <number value="12" square="144">  
  10.     <factor value="2" />  
  11.     <factor value="3" />  
  12.     <factor value="4" />  
  13.     <factor value="6" />  
  14.   </number>  
  15.   <number value="13" square="169" />  
  16.   <number value="14" square="196">  
  17.     <factor value="2" />  
  18.     <factor value="7" />  
  19.   </number>  
  20.   <number value="15" square="225">  
  21.     <factor value="3" />  
  22.     <factor value="5" />  
  23.   </number>  
  24. </numbers>  
Before we show the Groovy way of generating this, let’s look at how we’d do it in Java using the W3C DOM API. Don’t worry if you haven’t used DOM before—the idea isn’t to understand the details of the code, but to get an idea of the shape and complexity of the code required. To keep the example in listing 8.2 short, we’ll assume we’ve already constructed an empty Document , and we won’t do anything with it when we’ve finished. All we’re interested in is creating the data.
- Listing 8.2 Java snippet for producing the example XML
  1. // Java!  
  2. // … doc made available here …  
  3. Element numbers     = doc.createElement("numbers");  
  4. Element description = doc.createElement("description");  
  5. doc.appendChild(numbers);  
  6. numbers.appendChild(description);  
  7. description.setTextContent("Squares and factors of 10..15");  
  8. for (int i=10; i <15; i++)  
  9. {  
  10.     Element number = doc.createElement("number");  
  11.     numbers.appendChild(number);  
  12.     number.setAttribute("value",  String.valueOf(i));  
  13.     number.setAttribute("square", String.valueOf(i*i));  
  14.     for (int j=2; j < i; j++)  
  15.     {  
  16.         if (i % j  == 0)  
  17.         {  
  18.             Element factor = doc.createElement("factor");  
  19.             factor.setAttribute("value", String.valueOf(j));  
  20.             number.appendChild(factor);  
  21.         }  
  22.     }  
  23. }  
Note how there’s a lot of text in listing 8.2 that isn’t directly related to the data itself—all the calls to methods, and explicitly stating the hierarchy using variables. This is remarkably error-prone—just in creating this simple example, we accidentally appended two elements to the wrong place. The hierarchy isn’t evident, either—the numberselement appears at the same indentation level as the description element, despite one being a parent of the other. The loops create a feeling of hierarchy, but it’s only incidental—in a different example, they could be setting attributes on another element, without adding to the depth of the tree.

Now let’s look at the Groovy equivalent in listing 8.3. This is a complete script that writes the XML out to the console when it’s run. You’ll see later how simple it is to write the content elsewhere, but for the moment the default behavior makes testing the example easy.
- Listing 8.3 Using MarkupBuilder to produce the sample XML
  1. def builder = new groovy.xml.MarkupBuilder()  
  2. builder.numbers {  
  3.     description 'Squares and factors of 10..15'  
  5.     for (i in 10..15) {  
  6.         number (value: i, square: i*i) {   // Emit  number elements 10 through 15  
  7.             for (j in 2..<i) {  
  8.                 if (i % j == 0) {  
  9.                     factor (value: j)   // Emit each factor element  
  10.                 }  
  11.             }  
  12.         }  
  13.     }  
  14. }  
The example may feel slightly like magic at the moment. That’s a natural first reaction to builders, because we appear to be getting something almost for nothing. We generally view anything magical as somewhat suspicious—if it appears too good to be true, it usually is. As you’ll see, however, builders are clever but not miraculous. They use the language features provided by Groovy—particularly closures and meta-programming—and combine them to form an elegant coding pattern.

Create XML using Groovy MarkupBuilder





Where there is a will, there is a way!