Saturday 7 July 2012

Item 21: Use function objects to represent strategies


Some languages support function pointers, delegates, lambda expressions, or similar facilities that allow programs to store and transmit the ability to invoke a particular function.

Java does not provide function pointers, but object references can be used to achieve a similar effect. Invoking a method on an object typically performs some operation on that object. However, it is possible to define an object whose methods perform operations on other objects, passed explicitly to the methods. An instance of a class that exports exactly one such method is effectively a pointer to that method. Such instances are known as function objects. For example, consider the following class:

class StringLengthComparator {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}

This class exports a single method that takes two strings and returns a negative integer if the first string is shorter than the second, zero if the two strings are of equal length, and a positive integer if the first string is longer. This method is a comparator that orders strings based on their length instead of the more typical lexicographic ordering. A reference to a StringLengthComparator object serves as a “function pointer” to this comparator, allowing it to be invoked on arbitrary
pairs of strings. In other words, a StringLengthComparator instance is a concrete strategy for string comparison.

As is typical for concrete strategy classes, the StringLengthComparator class is stateless: it has no fields, hence all instances of the class are functionally equivalent. Thus it should be a singleton to save on unnecessary object creation costs (Item 3, Item 5):

class StringLengthComparator {
private StringLengthComparator() { }
public static final StringLengthComparator
INSTANCE = new StringLengthComparator();
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}

It would do no good to use StringLengthComparator because clients would be unable to pass any other comparison strategy. Instead, we need to define a Comparator interface and modify StringLengthComparator to implement this interface. In other words, we need to define a strategy interface to go with the concrete strategy class. Here it is:

// Strategy interface
public interface Comparator<T> {
public int compare(T t1, T t2);
}

The Comparator interface is generic (Item 26) so that it is applicable to comparators for objects other than strings. Its compare method takes two parameters of type T rather than String.

class StringLengthComparator implements Comparator<String> {
... // class body is identical to the one shown above
}

Concrete strategy classes are often declared using anonymous classes (Item 22). The following statement sorts an array of strings according to length:

Arrays.sort(stringArray, new Comparator<String>() {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});

But note that using an anonymous class in this way will create a new instance each time the call is executed. If it is to be executed repeatedly, consider storing the function object in a private static final field and reusing it. Another advantage of doing this is that you can give the field a descriptive name for the function object.

a static member class is used in preference to an anonymous class to allow the concrete strategy class to implement a second interface, Serializable:

// Exporting a concrete strategy
class Host {
private static class StrLenCmp
implements Comparator<String>, Serializable {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}

// Returned comparator is serializable
public static final Comparator<String>
STRING_LENGTH_COMPARATOR = new StrLenCmp();
... // Bulk of class omitted
}

The String class uses this pattern to export a case-independent string comparator via its CASE_INSENSITIVE_ORDER field.

To summarize, a primary use of function pointers is to implement the Strategy pattern. To implement this pattern in Java, declare an interface to represent the strategy, and a class that implements this interface for each concrete strategy. When a concrete strategy is used only once, it is typically declared and instantiated as an anonymous class. When a concrete strategy is designed for repeated use, it is generally implemented as a private static member class and exported in a public static final field whose type is the strategy interface.

Reference: Effective Java 2nd Edition by Joshua Bloch