In the DeptDirectory program, we failed to include the obvious method findPhone(String name) to look up a person's phone number. We could clearly implement findPhone(String name) in exactly the same way as we did findOffice(String name), but this would replicate code. A better strategy would be to implement a method Entry findEntry(String name) that returns the Entry matching a given name and then define both findPhone and findOffice in terms of findEntry.
We will pursue a third option because it illustrates a far more general approach to eliminating code replication. In this approach, we will define a method String findField(Operation f, String name) that takes an Operation object as an extra argument specifying which field to return. This approach mimics the familiar ``code factoring'' process in Scheme: repeated code patterns are abstracted into procedures that take differentiating code chunks are passed as closures (procedural arguments).
Code factoring cannot be implemented directly in Java because methods are not values that can be passed as arguments. Some object oriented languages such as Smalltalk and Self classify methods as data values, permitting code factoring to be implemented directly. Fortunately, it is not difficult to get around this restriction by explicitly representing closures as objects, albeit at the cost of wordier syntax. All we have to do is introduce an appropriate abstract class Operation containing a single abstract method execute( ... ) and and define a separate concrete subclass of Operation for different method that we want to pass an argument. Each concrete subclass defines execute appropriately. In the general case, the Operation subclasses may contain fields that correspond to the free variables appearing in procedural arguments in Scheme. These free variables must be bound when the Operation is constructed, exactly as they are in a language supporting procedures as data values.
In the object-oriented design literature, this technique is called the command pattern in homage to the dominant role that imperative operations play in object-oriented computation. Here we are using this pattern in a purely functional fashion, revealing the parochial biases of traditional object-oriented design.
To illustrate the command pattern, let us continue our DeptDirectory example. If we independently write findPhone and findOffice, they differ only in the field name used in the return expression.
class Empty extends DeptDirectory {
...
String findOffice(String name) {
return null;
}
String findPhone(String name) {
return null;
}
}
class Cons extends DeptDirectory {
...
String findOffice(String name) {
if (name.equals(first.name)) return first.office;
else return rest.findOffice(name);
}
String findPhone(String name) {
if (name.equals(first.name)) return first.phone;
else return rest.findPhone(name);
}
}
We can ``abstract out'' this difference by writing a single findField method embodying the common code pattern between findPhone and findOffice. To accomodate differing choices of returned Entry field, the method takes an Operation that performs the appropriate field extraction on the Entry. The following code includes a new mechanism for defining concrete subclasses that we have not discussed before, which we will explain in more detail below. In the class Operation, the static fields office and phone are bound to instances of new subclasses that define execute as the method extracting the office and phone fields, respectively, of an Entry.
abstract class Operation {
abstract String execute(Entry e);
static Operation office = new Closure() { // anonymous class
String execute(Entry e) { return e.office; }
}
static Operation phone = new Closure() { // anonymous class
String execute(Entry e) { return e.phone; }
}
}
abstract class DeptDirectory {
...
abstract String findField(Operation c, String name);
String findOffice(String name) {
return findField(Operation.office, name);
}
String findPhone(String name) {
return findField(Operation.phone, name);
}
class Empty extends DeptDirectory {
...
String findField(Operation c, String name) {
return null;
}
}
class Cons extends DeptDirectory {
...
String findField(Operation c, String name) {
if (name.equals(first.name)) return c.execute(first);
else return rest.findField(c,name);
}
}
Each brace construction
{ // anonymous class
String execute(Entry e) { return e. ...; }
}
following a new Operation()
expression above defines
a unique instance of a new anonymous
(unnamed) class extending Operation.
In Java, anonymous classes are simply an abbreviation mechanism.
The Operation class could have been written without anonymous
classes as follows:
abstract class Operation {
abstract String execute(Entry e);
static Operation office = new OfficeOperation();
static Operation phone = new PhoneOperation();
}
}
class OfficeOperation extends Operation {
String execute(Entry e) {
return e.office;
}
}
class PhoneOperation extends Operation {
String execute(Entry e) {
return e.phone;
}
}
at the cost of introducing the new class names OfficeOperation
and PhoneOperation.
In general, a single instance of a new class extending class C can be created using the notation:
new C(...) { ... members ...}where C(...) specifies what superclass initialization should be performed on the instance. No constructors can appear in the list of members because the class is nameless and cannot be instantiated again. Any required inititalization of fields inside the instance can be specified directly in the code defining the class.