Rice University - Comp 212 - Intermediate Programming

Fall 2002

Lecture #10 - Information Hiding and Abstract Factory Design Pattern


1. Information Hiding

Information hiding is a tried-and-true design principle that advocates hiding all implementation details of software components from the user in order to facilitate code maintenance.  It was first formulated by David L. Parnas (in 1971-1972) as follows.

By adhering to the above, code written by both users and implementors will have a high degree of flexibility, extensibility, interoperability and interchangeability.

The list framework that we have developed so far has failed to hide EmptyList and NEList, which are concrete implementations of AList, the abstract specification of the list structure.  In many of the list algorithms that we have developed so far, we need to call on EmptyList.Singleton or the constructor of NEList to instantiate concrete AList objects.  The following is another such examples.

InsertInOrder.java
import OOscheme.*;

/**
 * Inserts an Integer into an ordered host list, assuming the host list contains
 * only Integer objects.
 */
public class InsertInOrder implements IListAlgo {

    public static final InsertInOrder Singleton = new InsertInOrder();
    private InsertInOrder() {
    }

    /**
     * This is easy, don't you think?
     * @param inp an Integer to be inserted in order into host.
     */
    public Object emptyCase(AList host, Object inp) {
        return new NEList(inp, host);
    }

    /**
     * Recur!
     * @param inp an Integer to be inserted in order into host.
     */
    public Object nonEmptyCase(AList host, Object inp) {
        int n = ((Integer)inp).intValue();
        int f = ((Integer)host.getFirst()).intValue();
        return n < f ?
                new NEList(inp, host):
                new NEList(host.getFirst(), (AList)host.getRest().execute(this, inp));
    }
}

The above algorithm to insert in order an integer into an ordered list of integers can only be used for a very specific implementation of AList, namely the one that has EmptyList and NEList as concrete subclasses.  How can we write list algorithms that can be used for ANY implementation of the abstract specification of the list structure represented by the abstract class AList?  We can achieve our goal by applying the Abstract Factory Design Pattern to hide the concrete implementation from the user.

2. Abstract List Factory

Before we describe in general what the Abstract Factory Pattern is, let's examine what we have to do in the case of AList.

IListFactory.java
package schemeFactory;

/**
 * Abstract factory to manufacture empty and non-empty AList objects.
 */
public interface IListFactory {
    /**
     * Creates a concrete empty AList object.
     * @return AList.
     */
    public AList makeEmptyList();

    /**
     * Creates a concrete non-empty AList.
     * @param first the first data element for the AList
     * @param tail != null the rest for the AList
     * @return AList
     */
    public AList makeNEList(Object first, AList tail);
}

AList.java
package schemeFactory;
/**
 * Represents an abstract Scheme-like list, an  immutable linear recursive
 * structure of data. Has abstract methods to provide its internal data and
 * structure to the client and a hook to call back to any algorithm that
 * conforms to the IListAlgo interface.
 */
public abstract class AList {
    /**
     * Returns the first element in this AList, if any.
     * The behavior for the empty list is undefined.
     */
    public abstract Object getFirst();

    /**
     * Returns the tail ("rest") of this AList, if any.
     * The behavior for the empty list is undefined.
     */
    public abstract AList getRest();

    /**
     * "Hook" method to execute any IListAlgo visitor.
     * @param algo the algorithm operating on this AList.
     * @param inp the input needed by algo to perform its task.
     * @return the output Object of algo.
     */
    public abstract Object execute(IListAlgo algo, Object inp);

    /**
     * Calls ToString visitor to compute a String representation for AList.
     */
    public String toString() {
        return (String)execute(ToString.Singleton, null);
    }
}
IListAlgo.java

package schemeFactory;

/**
 * Represents an algorithm on the AList host.
 * Visitor pattern.
 */
public interface IListAlgo {
    /**
     * Perform an operation on the EmptyList host.
     * @param host an EmptyList
     * @param inp input needed by this method to perform the operation.
     * @return output Object of the operation to be performed.
     */
    public abstract Object emptyCase(AList host, Object inp);

    /**
     * Perform an operation on the NEList host.
     * @param host an NEList
     * @param inp input needed by this method to perform the operation.
     * @return output Object of the operation to be performed.
     */
    public abstract Object nonEmptyCase(AList host, Object inp);
}

AList, IListAlgo, and IListFactory prescribe a minimal and complete abstract specification of what we call a list software component.  We claim without proof that we can do anything with the list structure using this specification.

 

InsertInOrderWithFactory.java
import schemeFactory.*;

/**
 * Inserts an Integer into an ordered host list, assuming the host list contains
 * only Integer objects.  Has no knowledge of how AList is implemented.  Must
 * make use of a list factory (IListFactory) to create AList objects instead of
 * calling the constructors of concrete subclasses directly.
 */
public class InsertInOrderWithFactory implements IListAlgo {

    private IListFactory _listFact;

    public InsertInOrderWithFactory(IListFactory lf) {
        _listFact = lf;
    }

    /**
     * Simply makes a new non-empty list with the given inp parameter as first.
     * @param host an empty AList.
     * @param inp an Integer to be inserted in order into host.
     */
    public Object emptyCase(AList host, Object inp) {
        return _listFact.makeNEList(inp, host);
    }

    /**
     * Recur!
     * @param host a non-empty AList.
     * @param inp an Integer to be inserted in order into host.
     */
    public Object nonEmptyCase(AList host, Object inp) {
        int n = ((Integer)inp).intValue();
        int f = ((Integer)host.getFirst()).intValue();
        return n < f ?
                _listFact.makeNEList(inp, host):
                _listFact.makeNEList(host.getFirst(),
                                    (AList)host.getRest().execute(this, inp));
    }
}

The above algorithm only talks to the list structure it operates on at the highest level of abstraction specified by AList and IListFactory.  It does know and does not care how AList and IListFactory are implemented.  Yet it can be proved to be correct.  This algorithm can be plugged into any system that subscribes to the abstract specification prescribed by AList, IListAlgo, and IListFactory.

 

CompositeListFactory.java
Created with JBuilder
import schemeFactory.*;

/**
 * Concrete IListFactory to manufacture AList as composites.
 * EmptyList and NEList are static nested classes and hidden from all external
 * client code.  The implementations for EmptyList and NEList are the same as
 * before but completely invisible to the outside of this factory.
 * @author D.X. Nguyen
 */
public class CompositeListFactory implements IListFactory {

    /**
     * Note the use of private static.
     */
    private static class EmptyList extends AList {
        public final static EmptyList Singleton = new EmptyList ();

        private EmptyList() {
        }

        public Object getFirst() {
            throw new IllegalArgumentException("Empty list has no first!");
        }

        public AList getRest() {
            throw new IllegalArgumentException("Empty list has no first!");
        }
        public Object execute(IListAlgo algo, Object inp) {
            return algo.emptyCase(this, inp);
        }
    }

    /**
     * Note the use of private static.
     */
    private static class NEList extends AList {
        private Object _first;
        private AList _rest;

        public NEList(Object dat, AList tail) {
            _first = dat;
            _rest = tail;
        }

        public Object getFirst() {
            return _first;
        }

        public AList getRest() {
            return _rest;
        }

        public Object execute(IListAlgo algo, Object inp) {
            return algo.nonEmptyCase(this, inp);
        }
    }

    public AList makeEmptyList() {
        return EmptyList.Singleton;
    }

    public AList makeNEList(Object first, AList tail) {
        return new NEList(first, tail);
    }
}

Below is an example of a client that uses the InsertInOrderWithFactory algorithm.

ListClient.java 
import schemeFactory.*;
/**
 * Client for testing AList, IListAlgo, IListFactory, and InsertInOrderWithFactory.
 */
public class ListClient {

    /**
     * Creates an empty list and inserts a few integers in order.
     */
    public void run(IListFactory listFac) {
        IListAlgo algo = new InsertInOrderWithFactory(listFac);
        AList e0 = listFac.makeEmptyList();
        AList e1 = (AList)e0.execute(algo, new Integer(55));
        AList e2 = (AList)e1.execute(algo, new Integer(30));
        System.out.println(e2);
        AList e3 = (AList)e1.execute(algo, new Integer(100));
        System.out.println(e3);
        AList e4 = (AList)e2.execute(algo, new Integer(45));
        AList e5 = (AList)e4.execute(algo, new Integer(60));
        System.out.println(e5);
    }

    /**
     * Main entry point to test program.
     * @param nu not used.  May be used in future versions to pass in the name
     * of a concrete IListFactory.  We can then call on Java's dynamic class
     * loading mechanism to instantiate the concrete factory and pass it to the
     * run() method.
     */
    public static void main(String[] nu) {
        new ListClient().run(new CompositeListFactory());
    }
}

The above design process is an example of what is called the Abstract Factory Design Pattern.  The intent of this pattern is to provide an abstract specification for manufacturing a family of related objects (for examples, the empty and non-empty AList) without specifying their actual concrete classes thus hiding all details of implementation from the user.

Our example of the list structure framework successfully delineates specification from implementation and  faithfully adheres to the principle of information hiding.

However, the specification of IListAlgo as such lacks "robustness", in that it cannot prevent accidental calls to the host's getFirst() and getRest() in the emptyCase() method.  How can we improve on it?  A solution is to break up AList, the abstract list class, into a set of three interfaces: one for the empty list, one for the non-empty list, and one for the capability to perform algorithms.

3. The Ultimate Scheme List Framework

// Optional topic: omitted for now.

dxnguyen@cs.rice.edu
Copyright 2002, Dung X. Nguyen - All rights reserved.