程式扎記: [ In Action ] Working with the GDK - Working with Objects

標籤

2014年7月8日 星期二

[ In Action ] Working with the GDK - Working with Objects

Preface: 
Learning a new programming language is a twofold task: learning the syntax and learning the standard library. Whereas learning the syntax is a matter of days and getting proficient with new language idioms may require a matter of a few weeks, working through a new library can easily take several months. 

Luckily, no Java programmer needs to go through this time-consuming activity when learning Groovy. They already know most of the Groovy Standard Library, because that is the set of APIs that the Java Runtime provides. You can work with Groovy by solely using objects and methods as provided by the Java platform, although this approach doesn’t fully leverage the power of Groovy. 

Groovy extends (and in a few places modifies) the JRE to make it more convenient to work with, provide new dynamic features, and adapt the APIs to Groovy language idioms. The total of these extensions and modifications is called the GDK. Figure 9.1 gives the architectural overview. 
 
Figure 9.1 GDK’s place in the Groovy architecture 

A big part of the GDK concerns the datatypes that Groovy supports at the language level, such as strings, numbers, lists, and maps. That part of the GDK was covered in part 1 of this book. Here focuses on GDK capabilities that come from extending prominent JDK concepts such as Object , File , Stream , Thread , Process , and text processing with templates. 

Working with Objects: 
Java comes with a narrow API of 11 methods for its central abstraction java.lang.Object. These methods deal with the object lifecycle ( clone , finalize ), object equality (equals , hashCode ), information ( toString ), self-reflection ( getClass ), and multithreading support ( notify , notifyAll , three versions of wait ). 

Groovy adds much to the self-reflective and informational aspects of the API to better support live exploration of objects. It handles identity/equality differently and therefore needs to extend the respective API. It adds convenience methods to Object for the purpose of making these methods available anywhere in the code. Finally, it adds collection-aware methods to Object that are useful when the object can be seen as some kind of collection even though it is not necessarily of static typejava.util.Collection. This last category also includes the handling of object arrays. 

Interactive objects: 
When working on a program, you often need to inspect your objects, whether for debugging, logging, or tracing purposes. In dynamic languages such as Groovy, this need is even greater, because you may work with your programming language in an interactive fashion, asking your objects about their state and capabilities to subsequently send them messages. 

Object information in strings 
Often, the first task is to ask an object for some general information about itself: toString() in Java parlance. Groovy adds two more methods of this kind: 
■ dump returns a description of the object’s state, namely its fields and their values.
■ inspect makes a best effort to return the object as it could appear in Groovy source code, with lists and maps in the format of their literal declaration. If it cannot do better, it falls back to toString .

Listing 9.1 shows these methods called on a string that contains a single newline character. 
- Listing 9.1 Usage of dump and inspect 
  1. def newline = "\n"  
  2. assert newline.toString() == "\n"  
  3. assert newline.dump() ==  
  4. '''<java.lang.String@a value=  
  5. offset=0 count=1 hash=10>'''  
  6. assert newline.inspect() == /'\n'/  
If these methods are not sufficient when working with Groovy interactively, remember that you can fire up the graphical ObjectBrowser via 
  1. groovy.inspect.swingui.ObjectBrowser.inspect(obj)  
Which will popup UI likes: 
 

You have seen the dump method reveal the object’s fields and their values. The same and more can be done with the object’s properties. 

Accessing properties 
Remember that any Groovy object can be seen as a JavaBean, as you saw in section 7.4. You have already seen that its properties can be inspected with thegetProperties method or the properties property. The method returns a read-only map of property names and their current values. During inspection, printing the whole map of properties is as easy as: 
  1. println properties  // Or println someObj.properties  
When doing so, you may see more properties than you expected, because Groovy’s class-generation mechanism introduces accessors for that object’s class andMetaClass properties behind the scenes. 

Listing 9.2 shows property reflection in use. The example uses a class with a first property and a second read-only property that returns a derived value and is not backed by a field. A third property is only a field without accessor methods. The listing shows how to list all keys of that object’s properties. 
- Listing 9.2 Reflecting on properties 
  1. class MyClass {  
  2.     def first = 1                   // read-write property  
  3.     def getSecond() { first * 2 }   // read-only  property  
  4.     public third = 3                // public field property  
  5. }  
  6. obj = new MyClass()  
  7. keys = ['first','second','class']  
  8. assert obj.properties.keySet() == new HashSet(keys)  
  9.   
  10. // Properties map  
  11. assert 1 == obj.properties['first']  
  12. assert 1 == obj.properties.first  
  13.   
  14. // 1) Direct access  
  15. assert 1 == obj.first  
  16. assert 1 == obj['first']    // getAt('first')  
  17.   
  18. one = 'first'  
  19. two = 'second'  
  20. // 2) Dynamic assignment  
  21. obj[one] = obj[two]         // putAt(one)  
  22.   
  23. // 3) Field introspection  
  24. assert obj.dump() =~ 'first=2'  
At (1) and (2), you see that objects implement the getAt and putAt methods by default, such that the code appears to be accessing a map of properties as far as the subscript operator is concerned; d shows a simple way of introspecting an object via the dump method. Because the first property is backed by a field of the same name, this field and its current value appear in the dump. Note that this field is private and wouldn’t be visible otherwise. This trick is useful, especially in test cases. 
Note. 
When working with Groovy code, you may also come across Object ’s method getMetaPropertyValues. It is used internally with an object’s meta information and returns a list of PropertyValue objects that encapsulate the name, type, and value of a property.

Working with properties means working on a higher level of abstraction than working with methods or even fields directly. We will now take one step down and look at dynamic method invocation. 

Invoking methods dynamically 
In the Java world, methods (and fieldsbelong to Class rather than to Object. This is appropriate for most applications of reflection, and Groovy generally follows this approach. When you need information about an object’s methods and fields, you can use the following GPath expressions: 
  1. obj.class.methods.name  
  2. obj.class.fields.name  
This covers methods and fields as they appear in the bytecode. For dynamically added methods like those of the GDK, Groovy’s MetaClass provides the information: 
  1. obj.metaClass.metaMethods.name   
Note. You can add a .unique or .sort to the preceding GPaths to narrow down the list. 

Groovy follows a slightly different approach than Java when it comes to invoking these methods dynamically. You saw the invokeMethod functionality for GroovyObjects in section 7.1.2. The GDK makes this functionality ubiquitously available on any (Java) object. In other words, a Groovy programmer can call on any arbitrary object: 
  1. object.invokeMethod(name, params)  
Note. 
This simple call is much easier than JDK reflection, where you need to go through the Class object to fetch a Method object from a list, invoke it by passing it your object, and take care of handling numerous exceptions.

Dynamic method invocation is useful when the names of the method or its parameters are not known before runtime! 

Consider this scenario: You implement a persistence layer with Data Access Objects (DAOs), or objects that care for accessing persistent data. A Person DAO may have methods like findAll , findByLastName , findByMaximum , and so on. This DAO may be used in a web application setting as depicted in figure 9.2. It may respond to HTTP requests with request parameters for the type of find action and additional parameters. This calls for a way to dispatch from the request parameters to the method call. 
 

Such a dispatch can be achieved with if-else or switch constructions. Listing 9.3 shows how dynamic method invocation makes this dispatching logic a one-liner. Because this is only an illustrative example, we return the SQL statement strings from the DAO methods, not the Person objects as we would probably do in real DAOs. 
- Listing 9.3 Dynamic method invocation in DAOs 
  1. class PersonDAO {  
  2.     String findAll() {  
  3.         'SELECT * FROM Person'  
  4.     }  
  5.     String findByLastname(name) {  
  6.         findAll() + " WHERE p.lastname = '$name'"  
  7.     }  
  8.     String findByMaximum(attribute) {  
  9.         findAll() + " WHERE $attribute = " +  
  10.         "SELECT maximum($attribute) FROM Person"  
  11.     }  
  12. }  
  13. dao = new PersonDAO()  
  14.   
  15. action = 'findAll'  // some external input  
  16. params = [] as Object[]  
  17. assert dao.invokeMethod(action, params) == 'SELECT * FROM Person'  
The action and params variables refer to external input, such as from an HTTP request. Note that this example is characteristic for a variety of applications. Almost every reasonably sophisticated client-server application has to deal with this kind of dispatching and can thus benefit from dynamic method invocation. 

As a second example, an external configuration in plain-text files, tables, or XML may specify what action to take under certain circumstances. Think about domain specific languages (DSLs), the specification of a finite state machine, a workflow description, a rule engine, or a Struts configuration. Dynamic method invocation can be used to trigger such actions. 

These scenarios are classically addressed with the Command pattern. In this pattern, dynamic invocation can fully replace simple commands that only encapsulate actions (that is, they don’t encapsulate state or support additional functionality like undo). 

While we are on this topic, dynamic invocation can be applied not only to methods but also to closures. In order to select a closure by name, you can store such closures in properties. An idiomatic variant of listing 9.3 could thus be: 
  1. class PersonDAO {  
  2.     public findAll = {  
  3.         'SELECT * FROM Person'  
  4.     }  
  5.     // more finder methods as Closure fields ...  
  6. }  
  7. dao = new PersonDAO()  
  8. action = 'findAll'  // some external input  
  9. params = []  
  10. assert dao[action](*params) == 'SELECT * FROM Person'  
Note that findAll is now a public field with a closure assigned to it, dynamically accessed via dao[action]. This dynamically accessed closure can be called in various ways. We choose the shortest variant of putting parentheses after the reference, including any arguments. The * spread operator distributes the arguments over the closure parameters (if—unlike findAll —the closure has any parameters). 

These variants differ slightly in size where the closure variant is a bit shorter but may be less readable for the casual Groovy user. The closure variant additionally offers the possibility of changing the closure at runtime by assigning a new closure to the respective field. This can be handy in combination with the State pattern or Strategy pattern

Convenient Object methods: 
How often have you typed System.out.println when programming Java? In Groovy, you can achieve the same result with println , which is an abbreviation for this.println; and because the GDK makes println available on Object , you can use this anywhere in the code. This is what we call a convenience method. 

This section walks through the available convenience methods and their usage, as listed in table 9.1. 
 

Because Groovy uses the == operator for equality instead of identity checking, you need a replacement for the Java meaning of == . That is what the is method provides. In Java: 
  1. if ( a == b ) { /* more code here */}  
In Groovy: 
  1. if ( a.is(b)) { /* more code here */}  
The is method saves you the work of comparing the System.identityHashCode(obj) of a and b manually. 

The isCase method occurred often in the Groovy language description in part 1. For Object, the GDK provides a default implementation that checks for object equality. Note that this means you can use any (Java) object in a Groovy grep or switch: 
  1. switch(new Date(0)){  
  2.     case new Date(0) : println 'dates are equal'  
  3. }  
The identity method calls the attached closure with the receiver object as the closure’s delegate. This has an effect similar to that of the WITH keyword in Visual Basic. Use it when a piece of code deals primarily with only one object, like the following: 
  1. new Date().identity {  
  2.     println "$date.$month.$year"  
  3. }  
The properties date , month , and year will now be resolved against the current date. Such a piece of code has by definition the smell of inappropriate intimacy. This calls for making this closure a method on the receiver object, which you can do in Groovy with the use method as covered in section 7.5.3

The last convenience method in our list is sleep , which suspends the current thread for a given number of milliseconds. It enhances the JDK method Thread.sleep by automatically handling interruptions such that sleep is re-called until the given time has elapsed (as closely as the machine timer can tell). This makes the effective sleep time more predictable. 

If you want to handle interruptions differently, you can attach a closure that is called when sleep encounters an InterruptedException. With the sleep method, you can have some fun, as with the following example. Run it from the Groovy shell or console after predicting its output. Did you guess correctly what it does? 
  1. text = """  
  2. This text appears  
  3. slowly on the screen  
  4. as if someone would  
  5. tpye \b\b \b\b \b\b \bype it.  
  6. """  
  7. for (c in text) {  
  8.     sleep 100  
  9.     print c  
  10. }  
These are all methods that the GDK adds to every object for convenience. However, objects frequently come in a crowd. For such cases, the GDK provides methods to select them one-by-one, as shown in the next section. 

Iterative Object methods: 
In the Java world, any collection (in the general meaning of the word) of objects can support inspection of its contained items by providing an Iterator , a separate object that knows how to walk through that collection. Oh—wait, sometimes an Enumeration is used instead. As a further inconsistency, Iterators are not directly available on arrays and a lot of other common types. 

Even if you are lucky and found how to get an Iterator object in the API documentation, you cannot do much more with it than use it in a procedural way like this: 
  1. // Java !  
  2. for (Iterator collection.iterator(); iterator.hasNext(); ){  
  3.     MyClass obj = (MyClass) iterator.next();  
  4.     // do something with obj  
  5. }  
Groovy instead provides a simple and consistent way of doing this: 
  1. collection.each { /* do something with it */}   
Besides the simple each method, you can use any of the methods that are listed in table 9.2. 
 

What’s so useful about the methods in table 9.2 is that you can use them on any object you fancy. The GDK makes these methods available on Object and yields the respective items. As we described in section 6.3.2, this iteration strategy is also used in Groovy’s for loop. 

Getting the items is done with a best-effort strategy for the candidate types listed in table 9.3, where the first matching possibility is chosen. 
 

This allows for flexible usages of Groovy’s iteration-aware methods. There is no more need to care whether you work with an iterator, an enumeration, a collection, or whatever, for example within a GPath expression. 

Supplement: 
Documentation - Operator Overloading 
Documentation - User Guide - Operators

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!