next up previous
Next: Conditional Statements Up: The Union and Composite Previous: The Composite Pattern


Defining Instance Methods for a Composite Class

In Section 1.5.1, we showed how to define simple (instance) methods for the individual class Entry. But we did not show how to express operations that process all of the different forms of data defined by a composite hierarchy. Since each different form of data in a composite hierarchy is represented by a distinct concrete class, we can write a separate method definition for each kind of data.

Consider the following example. Assume that we want to define a method

String firstAddress(String name)
on DeptDirectory that returns the address for the first person in the directory if the directory is non-empty and the null reference null if it is empty. We can write separate definitions for the method firstAddress in the concrete classes Empty and Cons as follows:
class Empty {
  ...
  String firstAddress() { return null; }
}

class Cons {
  ...
  String firstAddress() { return this.first.getAddress(); }
}

Now assume that x is a variable of type DeptDirectory. If we try to invoke the method firstAddress on x, Java will reject the code as erroneous because the class DeptDirectory does not contain a method named firstAddress. How can we enlarge the definition of firstAddress so that it applies to the class DeptDirectory?

The answer is that we declare the method firstAddress in the class DeptDirectory as an abstract method:

abstract class DeptDirectory {
  ...
  /* firstAddress() returns the first address in a DeptDirectory;
       it returns null if the DeptDirectory is empty */
  abstract String firstAddress();
}

An abstract method is a method without a body. Abstract methods can only appear in abstract classes. Any class containing an abstract method must be declared abstract because it cannot be instantiated. Every concrete class extending an abstract class must provide concrete definitions for the abstract methods it inherits. This rule guarantees that abstract methods are never attached to objects.

Let us illustrate the process of defining a method over a composite class hierarchy in more detail by defining a method

String findAddress(String name)
on DeptDirectory that finds the address for the person identified by name, assuming that name is in the directory.

First, we must insert the following member somewhere in the class DeptDirectory

/* findAddress(s) returns the address of the person with name s;
     it returns null if no matching entry is found */
abstract String findAddress(String name);
The abstract modifier in the definition indicates that the definition only describes the input and output types for the method, not how it is implemented. Each concrete class extending DeptDirectory must provide a definition for findAddress that includes a code body describing how it is implemented.

The ordering of members within a class typically does not affect program behavior. Nevertheless, it is good programming practice to list class members in a consistent order. We recommend placing static final fields at the beginning of the class followed by dynamic members and finally static method (which have been discussed yet). Within each category, we recommend the following order: fields, constructors, methods. (Of course, there is no such thing as a static constructor.) According to this convention, the abstract method findAddress should be the last member in the DeptDirectory class.

Second, we must provide a concrete definition of the findAddress method in each concrete subclass of DeptDirectory, namely Empty and Cons. Note that the composite pattern guarantees that a program includes code specifically to process each data variant. Moreover, in any variant containing a field f of parent type, the method typically invokes itself recursively on f. This approach to defining methods is the direct analog of natural recursion template for defining functions in Scheme. It is so common and so important that OOP community has labeled it as a separate pattern, called the interpreter pattern, enriching the composite pattern.

Let us return to defining the findAddress method. By definition, there is no Entry in an Empty directory matching the name passed as an argument to findAddress. Hence, findAddress must return a value signaling failure. In Java, the most convenient choice for such a value is null, the reference to no object. All object values in Java are actually references, so the same object can simultaneously appear as the value of many different variables. Scheme follows exactly the same convention regarding structures. Java also provides the special value null, which is the reference to no object. There closest analog to null in Scheme is the special value (void). Java null should only be used to represent a special failure value. It should never be used to represent one of the alternatives in a data definition, e.g., the empty DeptDirectory. The reason for this prohibition is simple: null is not an object. Any attempt to invoke a method on null will generate a run-time error aborting program execution.

The following code is an appropriate definition of the findAddress method in the Empty class.

String findAddress(String name) { return null; }

The definition of findAddress for Cons objects is the only interesting chunk of code in this entire example. Since a Cons object always contains an Entry first and a DeptDirectory rest, the findAddress method must check to see if the passed name matches the name field of first and, depending on the outcome, either return the value of the address or recur on rest.

The object-oriented method has exactly the same recursive structure as the corresponding function definition.

The method can simply be coded as follows:

String findAddress(String name) {
  if (this.name.equals(this.first.getName())) 
    return this.first.getAddress();
  else return this.rest.findAddress(name);
}

Every class contains the instance method equals which takes a single argument of type Object. The Object class at the top of the class hierarchy defines this method. For a String object name, the equals method returns true if and only if the argument object contains exactly the same sequence of characters as name.

In the code above, the expression

this.name.equals(this.first.getName())
invokes the equals method of object in field name on the argument
this.first.getName().
The expression
this.first.getName()
invokes the getName method of the Entry object in the field first to get its name field. Similarly, the expression
this.first.getAddress()
invokes thSe getAddress method of the Entry object in field first to get its address field; the expression
this.rest.findAddress(name)
invokes the findAddress method of the DeptDirectory object in the rest field on the object name.

Notice that a Java instance method with $n$ arguments corresponds to a Scheme function of $n+1$ arguments because the object containing the method is the implicit argument this. Since the members of this are so frequently accessed in methods attached to the object this, Java allows the field prefix

this.
to be omitted! Hence, the definition of findAddress could have been written
String findAddress(String name) {
  if (name.equals(first.getName())) 
    return first.getAddress();
  else 
    return rest.findAddress(name);
}
Since explicit references to this clutter code making it more difficult to read, we will generally omit them. (In some situations, a method must refer to the entire receiver object this rather than one of its fields. In such a situation, the use of the keyword this is essential.)


Finger Exercise 1.6.3.1 Load the file DeptDirectory.java into the DrJava Definitions pane. Write a simple test class for this program. Compile the program and try some test lookups in the Interactions pane. Write a method findPhone analogous to findOffice and test it. Remember to use the same design steps that you learned for Scheme. Save your program in a file called DeptDirectory.java.


next up previous
Next: Conditional Statements Up: The Union and Composite Previous: The Composite Pattern
Corky Cartwright 2004-02-05