2014年4月10日 星期四

[ In Action ] Dynamic object orientation - Using power features

Preface:This section presents three power features that Groovy supports at the language level: GPath, the Spread operator, and the use keyword.

We start by looking at GPaths. A GPath is a construction in Groovy code that powers object navigation. The name is chosen as an analogy to XPath, which is a standard for describing traversal of XML (and equivalent) documents. Just like XPath, a GPath is aimed at expressiveness: realizing short, compact expressions that are still easy to read. GPaths are almost entirely built on concepts that you have already seen: field access, shortened method calls, and the GDK methods added to Collection. They introduce only one new operator: the *. spread-dot operator. Let’s start working with it right away.

Querying objects with GPaths:We’ll explore Groovy by paving a path through the Reflection API. The goal is to get a sorted list of all getter methods for the current object. We will do so step-by-step, so please open a groovyConsole and follow along. You will try to get information about your current object, so type:

and get a list of method names, returned as a list of string objects. You can easily work on it applying what you learned about strings, regular expressions, and lists. Because you are only interested in getter methods and want to have them sorted, type:

Such an expression is called a GPath. One special thing about it is that you can call the name property on a list of method objects and receive a list of string objects—that is, the names. The rule behind this is that:

where *. is called the spread-dot operator and member can be a field access, a property access, or a method call. The spread-dot operator is needed whenever a method should be applied to all elements of the list rather than to the list itself. It is equivalent to:

To see GPath in action, we step into an example that is reasonably close to reality. Suppose you are processing invoices that consist of line items, where each line refers to the sold product and a multiplicity. A product has a price in dollars and a name. An invoice could look like table 7.3.

Figure 7.1 depicts the corresponding software model in a UML class diagram. The Invoice class aggregates multiple LineItems that in turn refer to a Product.

Listing 7.23 is the Groovy implementation of this design. It defines the classes as GroovyBeans, constructs sample invoices with this structure, and finally uses GPath expressions to query the object graph in multiple ways. - Listing 7.23 Invoice example for GPath

The queries in listing 7.23 are fairly involved. The first, at (1), finds the total for each invoice, adding up all the line items. We then run a query, at (2), which finds all the names of products that have a line item with a total of over 7,000 dollars. Finally, query (3) finds the date of each invoice containing a purchase of the ULC product and turns it into a string. Note.

Table 7.4 gives you some metrics about both full versions, comparing lines of code (LOC), number of statements, and complexity in the sense of nesting depth

There may be ways to slim down the Java version, but the order of magnitude remains: Groovy needs less than 25% of the Java code lines and fewer than 10% of the statements! Writing less code is not just an exercise for its own sake. It also means lower chances of making errors and thus less testing effort. Whereas some new developers think of a good day as one in which they’ve added lots of lines to the codebase, we consider a really good day as one in which we’ve added functionality butremoved lines from the codebase.

In a lot of languages, less code comes at the expense of clarity. Not so in Groovy. The GPath example is the best proof. It is much easier to read and understand than its Java counterpart. Even the complexity metrics are superior.

Injecting the spread operator:Groovy provides a *spread operator that is connected to the spread-dot operator in that it deals with tearing a list apart. It can be seen as the reverse counterpart of the subscript operator that creates a list from a sequence of comma-separated objects. The spread operator distributes all items of a list to a receiver that can take this sequence. Such a receiver can be a method that takes a sequence of arguments or a list constructor.

What is this good for? Suppose you have a method that returns multiple results in a list, and your code needs to pass these results to a second method. The spread operator distributes the result values over the second method’s parameters:

This allows clever meshing of methods that return and receive multiple values while allowing the receiving method to declare each parameter separately. The distribution with the spread operator also works on ranges and when distributing all items of a list into a second list:

The spread operator eliminates the need for boilerplate code that would otherwise be necessary to merge lists, ranges, and maps into the expected format. You will see this in action in section 10.3, where this operator helps implement a user command language for database access.

Mix-in categories with the use keyword:Consider a program that reads two integer values from an external device, adds them together, and writes the result back. Reading and writing are in terms of strings; adding is in terms of integer math. You can’t write:

because this would result in calling the plus method on strings and would concatenate the arguments rather than adding them.

Groovy provides the use method, which allows you to augment a class’s available instance methods using a so-called category. In our example, we can augment the plus method on strings to get the required Perl-like behavior:

A category is a class that contains a set of static methods (called category methods). The use keyword makes each of these methods available on the class of that method’s first argument, as an instance method:

Because self is the first argument, the plus(operand) method is now available (or overridden) on the String class. Listing 7.24 shows the full example. It implements these requirements with a fallback in case the strings aren’t really integers and a usual concatenation should apply. - Listing 7.24 The use keyword for calculation on strings

The use of a category is limited to the duration of the attached closure and the current thread. The rationale is that such a change should not be globally visible to protect from unintended side effects. Throughout the language basics part of this book, you have seen that Groovy adds new methods to existing classes. The whole GDK is implemented by adding new methods to existing JDK classes. The use method allows any Groovy programmer to use the same strategy in their own code.

A category can be used for multiple purposes:

*To provide special-purpose methods, as you have seen with StringCalculationCategory , where the calculation methods have the same receiver class and may override existing behavior. Overriding operator methods is special.* To provide additional methods on library classes, effectively solving the incomplete library class smell.* To provide a collection of methods on different receivers that work in combination—for example, a new encryptedWrite method on java.io.OutputStream anddecryptedRead on java.io.InputStream.* Where Java uses the Decorator pattern, but without the hassle of writing lots of relay methods.*To split an overly large class into a core class and multiple aspect categories that are used with the core class as needed. Note that usecan take any number of category classes.

When a category method is assigned to Object, it is available in all objects—that is, everywhere. This makes for nice all-purpose methods like logging, printing, persistence, and so on. For example, you already know everything to make that happen for persistence:

Instead of Object , a smaller area of applicability may be of interest, such as all Collection classes or all your business objects if they share a common interface. Note that you can supply as many category classes as you wish as arguments to the use method by comma-separating the classes or supplying them as a list.

By now, you should have some idea of Groovy’s power features. They are impressive even at first read, but the real appreciation will come when you apply them in your own code. It is worth consciously bearing them in mind early on in your travels with Groovy so that you don’t miss out on some elegant code just because the features and patterns are unfamiliar. Before long, they will become so familiar that you will miss them a lot when you are forced to go back to Java. The good news is that Groovy can easily be used from Java, as we will explore in chapter 11.