1.4.3 Defining Instance Methods for a Composite Class
Keywords: current

Next: 1.4.4 Conditional Statements Up: 1.4 The Union and Previous: 1.4.2 The Composite Pattern

##
1.4.3 Defining Instance Methods for a Composite Class

In Section 1.3.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 so common and so important that OOP community has designated 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. Java also provides the special value `null`, which is the reference to no object. 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 (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

> `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 the `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`.

Since the members of `this` are so frequently accessed in methods of a class, 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 Load the text for the `DeptDirectory` example from you saved file into the DrJava Definitions pane. Edit the `Cons` class to construct some sample directories. Compile the program and try some test lookups in the Interactions pane. Write a method `findPhone` analogous to `findOffice` and test it on similar examples. Remember to use the design steps given in our design recipe.

Save your extended program in place of your original `DeptDirectory` program.

Next: 1.4.4 Conditional Statements Up: 1.4 The Union and Previous: 1.4.2 The Composite Pattern