Rice University - Comp 212 - Intermediate Programming

Spring 2005

Lecture #20 - Restricted Access Containers (RACs) and the Strategy Pattern


Introduction

Stacks and queues are examples of containers with special insertion and removal behaviors and a special access behavior.

Insertion and removal in a stack must be carried out in such a way that the last data inserted is the first one to be removed.   One can only retrieve and remove a data element from a stack by way of special access point called the "top".   Traditionally, the insertion and removal methods for a stack are called push and pop, respectively.  push inserts a data element at the top of the stack.  pop removes and returns the data element at the top of the stack.  A stack is used to model systems that exhibit LIFO (Last In First Out) insert/removal behavior. 

Data insertion and removal in a queue must be carried out in such a way that  the first one to be inserted is the first one to be removed.   One can only retrieve and remove a data element from a queue by way of special access point called the "front".  Traditionally, the insertion and removal methods for a queue are called enqueue and dequeue, respectively.  enqueue inserts a data element at the "end" of the queue.  dequeue removes and returns the data element at the front of the queue.  A queue is used to model systems that exhibit FIFO  (First In First Out) insertion/removal behavior.  For example, one can model a movie ticket line by a queue.

We abstract the behaviors of special containers such as stacks and queues into an interface called IRAContainer specified as follows.

Restricted Access Containers

IRAContainer.java 
package genRac;

import genListFW.*;

/**
 * Defines the interface for a restricted access container.
 */
public interface IRAContainer<T> {
    /**
     * Empty the container.
     * NOTE: This implies a state change.
     * This behavior can be achieved by repeatedly removing elements from this IRAContainer.
     * It is specified here as a convenience to the client.
     */
    public void clear();

    /**
     * Return TRUE if the container is empty; otherwise, return
     * FALSE. 
     * Question: do we really need this method?
     */
    public boolean isEmpty();

    /**
     * Return TRUE if the container is full; otherwise, return
     * FALSE. 
     */
    public boolean isFull();

    /**
     * Return an immutable list of all elements in the container.
     * @param fact for manufacturing an IList.
     */
    public IList<T> elements(IListFactory<T> fact);


    /**
     * Remove the next item from the container and return it.
     * NOTE: This implies a state change.
     * @throw an Exception if this IRAContainer is empty.
     */
    public T get();

    /**
     * Add an item to the container.
     * NOTE: This implies a state change.
     * @param input the data to be added to this IRAContainer.
     * @throw an Exception if this IRAContainer is full.
     */
    public void put(T input);

    /**
    * Return the next element in this IRAContainer withour removing it.
     * @throw an Exception if this IRAContainer is empty.
     */
    public T peek();
}

  1. Restrict the users from seeing inside or working on the inside of the container.
  2. Have simple put(data) and get() methods. Note the lack of specification of how the data goes in or comes out of the container.
  3. However, a "policy" must exist that governs how data is added ("put") or removed ("get"). Examples:
    1. First in/First out (FIFO) ("Queue")
    2. Last in/First out (LIFO) ("Stack")
    3. Retrieve by ranking ("Priority Queue")
    4. Random retrieval
  4. The policy is variant behavior --> abstract it.
    1. The behavior of the RAC is independent of exactly what the policy does.
    2. The RAC delegates the actual adding ("put") work to the policy.
    3. The RAC is only dependent on the existence of the policy, not what it does.
    4. The policy is a "strategy" for adding data to the RAC. See the Strategy design pattern.
    5. Strategy pattern vs. State pattern -- so alike, yet so different!

The manufacturing of specific restricted access containers with specific insertion strategy will be done by concrete implementations of the following abstract factory interface.

IRACFactory.java
package genRac;

/**
 * Abstract Factory to manufacture RACs.
 */
public interface IRACFactory<T> {
    /**
     * Returns an empty IRAContainer.
     */
    public IRAContainer<T> makeRAC();
}

Examples

The following is an (abstract) implementation of IRACFactory using the generic LRStruct as the underlining data structure.  By varying the insertion strategy, which is an IAlgo on the internal LRStruct, we obtain different types of RAC: stack, queue, random, etc. 

NOTE: Due to the limitation of our UML tool, the UML class diagram shown below does not show the generic type of all the classes.

 

ALRSRACFactory.java
package genRac;

import genListFW.*;
import genListFW.factory.*;
import genLRS.*;

/**
 * Implements a factory for restricted access containers.  These
 * restricted access containers are implemented using an generic LRStruct to
 * hold the data objects.
 * Click here for the public methods of the generic LRStruct and visitor.
 */
public abstract class ALRSRACFactory<T> implements IRACFactory<T> {

    /**
     * Implements a general-purpose restricted access container using
     * a generic LRStruct.  How? 
     *
     * The next item to remove is always at the front of the list of
     * contained objects.  This is invariant!
     *
     * Insertion is, however, delegated to a strategy routine; and
     * this strategy is provided to the container.  This strategy
     * varies to implement the desired kind of container, e.g., queue
     * vs. stack.
     *
     * This nested static class is protected so that classes derived from its
     * factory can reuse it to create other kinds of restricted access
     * container. 
     */
    protected static class LRSRAContainer<T> implements IRAContainer<T> {
        private IAlgo<T, Object, T> _insertStrategy;
        private LRStruct<T> _lrs;

        // anonymous inner class to check for emptiness!
	private IAlgo<Object, Boolean, Object> _checkEmpty = new IAlgo<Object, Boolean, Object>() {

            public Boolean emptyCase(LRStruct<? extends Object> host, Object... input) {
                 return Boolean.TRUE;
            }    

            public Boolean nonEmptyCase(LRStruct<? extends Object> host, Object... input) {
                 return Boolean.FALSE;
           }        
        };

        public LRSRAContainer(IAlgo<T, Object, T> strategy) {
            _insertStrategy = strategy;
            _lrs = new LRStruct<T>();
        }

        /**
         * Empty the container.
         */
        public void clear() {
            _lrs = new LRStruct<T>();
        }  

        /**
         * Return TRUE if the container is empty; otherwise, return
         * FALSE.
         */
        public boolean isEmpty() {
            return _lrs.execute(_checkEmpty);
        }

        /**
         * Return TRUE if the container is full; otherwise, return
         * FALSE. 
         *
         * This implementation can hold an arbitrary number of
         * objects.  Thus, always return false.
         */
        public boolean isFull() {
            return false;
        }

        /**
         * Return an immutable list of all elements in the container.
         */
        public IList<T> elements(final IListFactory<T> fact) {

            return _lrs.execute (new IAlgo<T, IList<T>, Object>() {
               
                public IList<T> emptyCase(LRStruct<? extends T> host, Object... nu) {
                    return fact.makeEmptyList();
                }
                
                public IList<T> nonEmptyCase(LRStruct<? extends T> host, Object... nu) {
                    return fact.makeNEList(host.getFirst(),
                                           host.getRest().execute(this));
                }
            });
        }  

        /**
         * Remove the next item from the container and return it.
         */
        public T get() {
            return _lrs.removeFront();
        }        

        /**
         * Add an item to the container.
         */
        public void put(T input) {
            _lrs.execute(_insertStrategy, input);
        }        

        public T peek() {
            return _lrs.getFirst();
        }
    }    
}

LRSStackFactory.java
package genRac;

import genLRS.*;

public class LRSStackFactory<T> extends ALRSRACFactory<T> {
    /**
     * Create a ``last-in, first-out'' (LIFO) container.
     */
    public IRAContainer<T> makeRAC() {

        return new LRSRAContainer<T> (new IAlgo<T, Object, T>() {

            public Object emptyCase(LRStruct<? extends T> host, T... input) {
                return ((LRStruct<T>)host).insertFront(input[0]);
            }
            
            public Object nonEmptyCase(LRStruct<? extends T> host, T... input) {
                return ((LRStruct<T>)host).insertFront(input[0]);
            }
        });
    }
}

LRSQueueFactory.java
package genRac;

import genLRS.*;

public class LRSQueueFactory<T> extends ALRSRACFactory<T> {

    /**
     * Create a ``first-in, first-out'' (FIFO) container.
     */
    public IRAContainer<T> makeRAC() {

        return new LRSRAContainer<T> (new IAlgo<T, Object, T>() {

            public Object emptyCase(LRStruct<? extends T> host, T... input) {
                return ((LRStruct<T>)host).insertFront(input[0]);
            }
            
            public Object nonEmptyCase(LRStruct<? extends T> host, T... input) {
                return ((LRStruct<T>)host).getRest().execute(this, input[0]);
            }
        });
    }    
}
RandomRACFactory.java
package genRac;

import genLRS.*;

/*
 * Implements a factory for restricted access containers, including a
 * container that returns a random item.
 */
public class RandomRACFactory<T> extends ALRSRACFactory<T> {
    /**
     * Create a container that returns a random item.
     */
    public IRAContainer<T> makeRAC() {

        return new LRSRAContainer<T> (new IAlgo<T, Object, T>() {

            public Object emptyCase(LRStruct<? extends T> host, T... input) {
                return ((LRStruct<T>)host).insertFront(input[0]);
            }

            public Object nonEmptyCase(LRStruct<? extends T> host, T... input) {
                /*
                 * Math.Random returns a value between 0.0 and 1.0.
                 */
                if (0.75 > Math.random())
                    return ((LRStruct<T>)host).insertFront(input[0]);
                else
                    return ((LRStruct<T>)host).getRest().execute(this, input[0]);
            }
        });
    }
}