Rice University - Comp 212 - Intermediate Programming

Fall 2001

Lecture #6 - Immutable Recursive Structure and The Composite Pattern

I. Structure

The list structure is one of the most fundamental data structures in programming.   The following is a common definition of a list.

A list is either empty or non-empty.  If it is empty it contains no elements.   If is not empty, it contains an element called first and an element called rest, which is itself a list.

We rephrase the above as follows.

There is an abstract notion of a list.  A list knows how to return the element called first and the element called rest, which is itself another list.
The empty list is a list.  It has no first and no rest element.
A non-empty list is a list.  It has an element called first and an element called rest, which is itself another list.

We model this abstract notion of a list by an abstract class called AList, the empty list and the non-empty lists as a concrete subclasses of AList called EmptyList and NEList respectively.  The following UML diagram illustrates this structural design.

List.png (5826 bytes)

In the above diagram, the methods getFirst() and getRest() in AList are pure abstract, that is they contain no code body.  They are overriden in the concrete subclasses NEList and EmptyList with concrete code body to carry out the appropriate tasks.  The client should program only to the abstract class  AList.   Polymorphism will properly dispatch the method call to the appropriate concrete subclass at run-time.

II. Design Patterns

Singleton Pattern

The EmptyList class is implemented with the singleton pattern to model the fact that there is a unique empty list.

Composite Pattern

An NEList object "has a" an element, called rest, whose structure is isomorphic to the enclosing NEList object itself..  NEList is said to be a composite.  Its structure is recursively constructed and terminates with the "base case", the EmptyList.   The above taxonomy is an example of what is called the composite design pattern.  The composite pattern is a structural pattern that prescribes how to build a container object that is composed of other objects whose structure is isomorphic to that of the container itself.  In this pattern, the container is called a composite.  The composite pattern also prescribes a coding pattern for the container's methods: when a container is called to perform an operation, it iterates through its list of composed objects and call on them to perform the same operation.  It allows the client to treat the container and what it contains uniformly by making use of polymorphism.  The following UML diagram illustrates the general composite pattern.

composite.gif (7317 bytes)

In many situations, an operation may call on "helper" operations on the sub-components to carry out the task.  The coding pattern for an  operation that requires a helper is as follows.

class Composite extends AComponent {
    // fields and sub-components.

    public Object doTask (Object input) {
      // Process the input parameter and the data fields to obtain a temporary result needed by the sub-components to help complete the task:
        Object temp = process (data fields, input);

      // Pass the temporary result to each of the sub-components asking each of them to "help" complete the task:
        Object pr1 = _part1.helpDoTask (temp); // partial result 1.
        Object pr2 = _part2.helpDoTask (temp);
        // etc...
        Object prN = _partN.helpDoTask (temp);

        // Reassemble the partial results computed by the sub-components to obtain the final result:
        return putTogether (pr1, pr2, ..., prN);
    }

    protected Object helpDoTask (Object input) {
      // Process the input parameter and the data fields to obtain a temporary result needed by the sub-components to help complete the task:
        Object temp = processPartial (data fields, input);

      // Pass the temporary result to each of the sub-components asking each of them to "help" complete the task:
        Object pr1 = _part1.helpDoTask (temp); // partial result 1.
        Object pr2 = _part2.helpDoTask (temp);
        // etc...
        Object prN = _partN.helpDoTask (temp);

        // Reassemble the partial results computed by the sub-components to obtain the final result:
        return putTogetherPartial (pr1, pr2, ..., prN);
    }
}

class Basic extends AComponent {
    // fields

    public Object doTask (Object input) {
      // Process the input parameter and the data fields to obtain the result:
        return simpleProcess (data fields, input);
    }

    protected Object helpDoTask (Object input) {
      // Process the input parameter and the data fields to obtain the result:
        return simpleProcessPartial (data fields, input);
    }
}

dxnguyen@cs.rice.edu, last revised 09/10/01
Dung X. Nguyen - Copyright 2001 - All rights reserved