Rice University - Comp 212 - Intermediate Programming

Fall 2002

Lecture #6 - List Structure Framework

Today's menu:

  1. The list structure as a framework.
  2. More examples of list algorithms, IListAlgo.  In class exercises.

About Frameworks

The following is a direct quote from the Design Patterns book by Gamma, Helm, Johnson, and Vlissides (the Gang of Four - GoF).

"Frameworks thus emphasizes design reuse over code resuse...Reuse on this level leads to an inversion of control between the application and the software on which it's based. When you use a toolkit (or a conventional subroutine library software for that matter), you write the main body of the application and call the code you want to reuse. When you use a framework, you reuse the main body and write the code it calls...."

The linear recursive structure (IList, IEmptyList, INEList), called the "host", coupled with the interface IListAlgo, called the "visitor", as shown in the above is one of the simplest, non-trivial, and practical examples of frameworks.  It has the characteristic of "inversion of control" described in the quote. It illustrates the so-called Holywood Programming Principle: don't call me, I will call you. Imagine (IList, IEmptyList, INEList) sitting in a library. When we write an algorithm on an IList  in conformance with its visitor interface IListAlgo, we are writing code for the IList to call and not the other way around. By adhering to the IList framework's protocol, all algorithms on the IList can be developed much more quickly. And because they all have similar structures, they are much easier to "maintain". 

Polymorphism

IList is said to be polymorphic because it represents the union of IEmptyList and INEList objects.  Yet, one never has to check for the type of the list when operating on the list.  This reduces flow control and code complexity.

Example 1

Compute the length of a list.

package listFW.visitor;

import listFW.*;

/**
 * Computes the number of elements in a list.
 * @author D. X. Nguyen
 * @since Copyright 2002 - DXN All rights reserved
 */
public class GetLength implements IListAlgo {

    /**
     * Returns 0 because the empty list has no elements.
     * @param host an empty list.
     * @param inp not used.
     * @return Integer(0)
     */
    public Object emptyCase(IEmptyList host, Object inp) {
        return new Integer(0);
    }


    /**
     * Recursively computes the length of host's rest, adds 1 to the result and
     * returns.
     * @param host a non-empty list.
     * @param inp not used.
     * @return Integer
     */
    public Object nonEmptyCase(INEList host, Object inp) {
        // NOTE the use of "type-casting" below.
        Integer restLen = (Integer)host.getRest().execute(this, null);
        // The execute() method for IList returns an Object in general.
        // However, we do know that host.getRest().execute(...) returns an
        // Integer.  Type-casting to an Integet tells the compiler to assume
        // that the returned Object is an Integer.

        return new Integer(1 + restLen.intValue());
    }
}

Abstract Factory Design Pattern

In order to test the above algorithm, GetLength, we need to have a way to manufacture concrete IList objects.  This is where the notion of "factories" comes in.  The interface IListFactory specifies in the abstract the methods for manufacturing the two kinds of IList objects: IEmptyList and INEList.  This is an example of what is called the Abstract Factory Design Pattern: whenever we have a family of classes that descend from some abstract class or interface, we should have an abstract factory to manufacture those classes.

package listFW;

/**
 * Abstract factory to manufacture IEmptyList and INEList.
 * @author Dung X. Nguyen
 * @author Stephen B. Wong
 * @since Copyright 2002 - DXN, SBW All rights reserved
 */
public interface IListFactory {
    /**
     * Creates an empty list.
     * @return an IEmptyList object.
     */
    public abstract IEmptyList makeEmptyList();


    /**
     * Creates a non-empty list containing a given first and a given rest.
     * @param first a data object.
     * @param tail the rest of the non-empty list to be manufactured.
     * @return an INEList object containing first and tail
     */
    public abstract INEList makeNEList(Object first, IList tail);
}

CompositeListFactory is a concrete implementation of IListFactory. 

package listFW.factory;

import listFW.*;

/**
 * Concrete IListFactory to manufacture IList objects.  How it does it is
 * completely hidden from all external client code.
 * The Singleton pattern is used ensure there is only one instance of this
 * factory.  This saves time and space.
 * To use this factory, simply call CompositeListFactory.Singleton.
 * @author D.X. Nguyen
 */
public class CompositeListFactory implements IListFactory {

    /**
     * Singleton Design Pattern:
     */
    public static final CompositeListFactory Singleton
                        =  new CompositeListFactory();

    /**
     * private constructor makes it impossible for external code to instantiate
     * any copy of this factory object.  Only the code inside of this class can
     * call this constructor, which is what the above line of code does.
     */
    private CompositeListFactory() {
    }

    // A bunch of code to implement IEmptyList and INEList elided...

    public IEmptyList makeEmptyList() {
        // code elided.
    }

    public INEList makeNEList(Object first, IList tail) {
        // code elided.
    }
}

Information Hiding

We hide the code for CompositeListFactory for now.  The point we try to make is that a user can call on this factory to manufacture concrete IList objects (IEmptyList objects and INEList objects), without knowing how the details of the manufacturing process.  This helps realize an important design principle in software engineering, called information hiding.  We will see more of this principle throughout the course.

Below is an example of a client program that tests the GetLength algorithm.  Pay attention to the run method of this algorithm and note how it does not care what factory its using.  Also note that the list to be tested is declared as IList and not any specific IEmptyList or INEList.  This illustrates another important software engineering principle: program to the highest level of abstraction.

import listFW.*;
import listFW.visitor.*;
import listFW.factory.*;

/**
 * Client to test the Length class on the empty list, a one-element list, and
 * a two-element list
 * @author DXN
 * @since Copyright 2002 - DXN All rights reserved
 */
public class TestLength {

    /**
     * Calls the GetLength algorithm to calculate the length of a list and
     * prints the result.
     * @param list != null, an IList whose length is to be computed
     * and printed.
     */
    private void testLength(IList list) {
        System.out.println(list + " has length " + list.execute(new GetLength(), null));
    }

    /**
     * Creates the empty list, a one-element list, a two-element list and
     * prints out their lengths.
     */
    public void run(IListFactory f) {
        IList e = f.makeEmptyList();
        // NOTE that e is declared as an IList and not an IEmptyList.
        // This way, later e can be assigned an INEList.
        // e is said to be polymorphic.
        // Also, IList is said to be polymorphic.
        testLength(e);
        e = f.makeNEList("one", e);
        testLength(e);
        e = f.makeNEList("two", e);
        testLength(e);
    }

    /**
     * Main entry point to test program.
     * @param nu not used.
     */
    public static void main(String[] nu) {
        new TestLength().run(CompositeListFactory.Singleton);
    }
}

Example 2

Concatenating two lists. 

package listFW.visitor;

import listFW.*;

/**
 * Concatenates the host with the input parameter.
 * An IListFactory is passed to the constructor.
 * @author D.X. Nguyen
 * @since Copyright 2002 DXN - All rights reserved
 */
public class ConcatWith implements IListAlgo{
    private IListFactory _fact;

    public ConcatWith (IListFactory f) {
        _fact = f;
    }

    /**
     * Returns the input list since the host is empty.
     * @param host an IEmptyList.
     * @param rhs IList on the right hand side.
     * @return rhs
     */
    public Object emptyCase(IEmptyList host, Object rhs) {
        return rhs;
    }

    /**
     * Cons host's first with the concatenation of host's rest.
     * @param host an INEList.
     * @param rhs IList on the right hand side
     * @return INEList
     */
    public Object nonEmptyCase(INEList host, Object rhs) {
        IList restConcat = (IList)host.getRest ().execute (this, rhs);
        return _fact.makeNEList(host.getFirst (), restConcat);
    }
}

Note how an abstract factory is passed to the constructor of ConcatWith.  The algorithm needs to make use of such a factory to build IList objects and works for any IListFactory.

 

 

Example 3

Find the last element of a list.

Note the use of a "helper algorithm" to  help complete the task.

package listFW.visitor;

import listFW.*;

/**
 * Computes the last element in the host by passing the host's first down to
 * the host's rest and asks for help to find the last element, in the case the
 * host is not empty.  The case of an empty host is trivial: no such element!
 * @author D. X. Nguyen
 */
public class LastElement implements IListAlgo {
    public static final LastElement Singleton = new LastElement ();

    private LastElement () {
    }

    /**
    * Throws an IllegalArgumentException since the empty list has no element.
    * @param host  not used
    * @param input not used
    * @return does not return
    * @exception IllegalArgumentException
    */
    public Object emptyCase(IEmptyList host, Object input) {
        throw new IllegalArgumentException ("Empty list has no data!");
    }

    /**
    * Passes the host's fist to the host's rest and asks for help to figure out
    * what the last element is.
    * @param host a non-empty list
    * @param input not used
    * @return Object the last element in the host
    */
    public Object nonEmptyCase(INEList host, Object input) {
        return host.getRest ().execute(HelpGetLast.Singleton, host.getFirst ());
    }
}

HelpGetLast.java
Created with JBuilder

package listFW.visitor;

import listFW.*;

/**
 * Helps compute the last element in the list containing the host.
 * The input parameter is the last element of the preceding list.
 * @author D. X. Nguyen
 */
public class HelpGetLast implements IListAlgo {
    public static final HelpGetLast Singleton = new HelpGetLast ();

    private HelpGetLast () {
    }

    /**
     * Returns the input parameter since this is the end of the list.
     * @param host not used
     * @param input the first element of the host's parent list.
     * @return Object
     */
    public Object emptyCase(IEmptyList host, Object input) {
        return input;
    }

    /**
     * Passes the host's first down to the host's rest and asks for help to
     * figure out the last element in the host: recur!
     * @param host a non-empty list
     * @param input not used.
     * @return Object
     */
    public Object nonEmptyCase(INEList host, Object input) {
        return host.getRest().execute(this, host.getFirst());
    }
}

 

 

dxnguyen@cs.rice.edu,  last revised 09/09/02