Each time we want to compute something new, we have to edit each class and add appropriate methods to each class.
Class AList is calle the host and its method execute() is called a "hook" to the IListAlgo visitors. Each time we want to compute something new, we simply add an appropriate concrete visitor and ask the list to call on the visitor.
Without Visitor |
With Visitors |
||
public
abstract class
AList { public abstract Object getFirst(); public abstract AList getRest(); // Has a bunch of methods, each corresponds to a specific computation. } |
public abstract
class AList { public abstract Object getFirst(); 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); } |
||
public
class EmptyList extends
AList { // Other methods elided... /** * Calls algo's empty case method. */ public Object execute(IListAlgo algo, Object inp) { return algo.emptyCase(this, inp); } } |
public
class NEList extends
AList { private Object _first; private AList _rest; // Other methods elided... /** * Calls algo's non-empty case. */ public Object execute(IListAlgo algo, Object inp) { return algo.nonEmptyCase(this, inp); } } |
||
public
class EmptyList extends
AList { // Implementation of each method listed in AList. } |
public class
NEList extends AList { private Object _first; private AList _rest; // Implementation of each method listed in AList. } |
||
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); |
||
Compute the length of a list |
|||
Add an abstract method called getLength() to AList.
|
Create a concrete IListAlgo visitor called GetLength.
|
||
public
class EmptyList extends
AList { // Other code elided... public int getLength() { return 0; } |
public
class GetLength implements
IListAlgo { // Singleton Pattern; public Object emptyCase(AList host, Object inp) { return new Integer(0); } |
||
public class
NEList extends AList { // Other code elided... public int getLength() { return 1 + _rest.getLength(); } |
public Object nonEmptyCase(AList host, Object inp) { // Note how the recursive call is made below: Integer restLen = (Integer)host.getRest().execute(this, null); return new Integer(1 + restLen.intValue()); } } |
||
Client Code |
|||
AList someList = blah-blah-blah; someList.getLength(); |
AList someList = blah-blah-blah; someList..execute(GetLength.Singleton, null); |
||
Compute the minimum of a list using a helper |
|||
Add an abstract method called getMin() and helpGetMin(int acc) to
AList.
|
Create concrete IListAlgo visitors called GetMin and GetMinHelper.
|
||
public
class EmptyList extends
AList { // Other code elided... public int getMin() { throw new IllegalArgumentException("Empty list has no data."); } } |
public
class GetMin implements
IListAlgo { // Singleton Pattern. public Object emptyCase(AList host, Object inp) { throw new IllegalArgumentException("EmptyList has no data!"); } // non-empty case elided... } |
||
public class
NEList extends AList { // Other code elided... public int getMin() { return _rest.helpGetMin(((Integer)_first).intValue()); } } |
public
class GetMin implements
IListAlgo { // Singleton Pattern. // emptyCase elided... public Object nonEmptyCase(AList host, Object inp) { return host.getRest().execute(GetMinHelper.Singleton, host.getFirst()); } |
||
public
class EmptyList extends
AList { // Other code elided... protected int helpGetMin(int tempMin) { return tempMin; } } |
public
class GetMinHelper implements
IListAlgo { // Singleton Pattern. public Object emptyCase(AList host, Object inp) { return inp; } // non-empty case elided ... } |
||
public class
NEList extends AList { // Other code elided... protected int helpGetMin(int accMin) { return _rest.helpGetMin(Math.min(((Integer)_first).intValue(), accMin)); } } |
class
GetMinHelper implements IListAlgo { // Singleton Pattern. // emptyCase elided... public Object nonEmptyCase(AList host, Object inp) { // Note how and why type casting is used below: int f = ((Integer)host.getFirst()).intValue(); int i = ((Integer)inp).intValue(); // Note how a recursive call is made below: return host.getRest().execute(this, new Integer(Math.min(f, i))); } |
||
Client Code |
|||
AList someList = blah-blah-blah; someList.getMin(); |
AList someList = blah-blah-blah; someList..execute(GetMin.Singleton, null); |
||
Procedural (read non OO) programming often requires traversing a data structure in some way in order to process the data elements in the structure. The coding pattern looks as follows.
while (structure has more elements) {
get next data element from the structure and process it;
}
In order to hide the details of implementation of the data structure but still provide a way to "iterate" through the data structure, we should provide an object that has intimate knowledge of the structure and knows how to traverse it. Such an object is called an iterator of the structure. Java provide two interfaces that serve to iterate through a collection of data elements: java.util.Enumeration and java.util.Iterator. See the Java API for more details.
We now give an example of an Enumeration for our list structure, though our structure already has all the necessary behaviors for any client to traverse it.
import java.util.Enumeration; import OOscheme.*; /** * Enumerates the objects contained within a list. Also serves as a visitor * to check the host list for emptiness. * Serves an an example of visitor with states. * Illustrates the use of multiple inheritance. * @author Alan Cox */ public class ListEnumerator implements Enumeration, IListAlgo { private AList _list; /** * Initializes this Enumeration with the AList to be enumerated. * @param list the AList to be enumerated. */ public ListEnumerator(AList list) { _list = list; } /** * Returns true if and only if their are more elements to * enumerate. Otherwise, returns false. * @return boolean */ public boolean hasMoreElements() { return ((Boolean)_list.execute(this, null)).booleanValue(); } /** * Returns the next element. * @return Object */ public Object nextElement() { Object first = _list.getFirst(); _list = _list.getRest(); return first; } /** * Returns Boolean.FALSE to indicate that the list has no more * elements to enumerate. * @param host an EmptyList * @param inp not used * @return Boolean */ public Object emptyCase(AList host, Object inp) { return Boolean.FALSE; } /** * Returns Boolean.TRUE to indicate that the list has more * elements to enumerate. * @param host an NEList * @param inp not used * @return Boolean */ public Object nonEmptyCase(AList host, Object inp) { return Boolean.TRUE; } }dxnguyen@cs.rice.edu Copyright 2002, Dung X. Nguyen - All rights reserved.