COMP 310
Spring 2018

Using a Visitor to Process Two Unknown Hosts

Home  Info  Canvas   Java Resources  Eclipse Resources  Piazza

Here's a more complex example that shows how delegation model programming is used to solve a problem that would otherwise require a complicated if-else stack:

First, consider a world where there are two types of students:  South College students and North College students:

/**
 * Abstract student
 */
public interface IStudent {
	// other methods elided
	
	/**
	 * The Visitor Design Pattern's "accept" method. 
	 * @param algo The visitor this host is to run
	 * @param params A vararg of arbitrary parameters for the visitor to use.
	 * @return The results from running the visitor 
	 */ 
	public abstract Object execute(IStudentVisitor algo, Object...params);
}

/**
 * A concrete South College student 
 */
public class SouthStudent implements IStudent {
	// methods elided
	
	/**
	 * A South College student ALWAYS calls the southCase() of their visitor!
	 */
	@Override
	public Object execute(IStudentVisitor algo, Object...params) {
		return algo.southCase(this, params);  
	}
}

/**
 * A concrete North College student 
 */
public class NorthStudent implements IStudent {
	// methods elided
	
	/**
	 * A North College student ALWAYS calls the northCase() of their visitor!
	 */
	@Override	
	public Object execute(IStudentVisitor algo, Object...params) {
		return algo.northCase(this, params); 
	}
}

There is, of course, a corresponding visitor with a case for each host:

/**
 * A visitor to an IStudent that represents an arbitrary algorithm on 
 * a single student host.
 */
public interface IStudentVisitor {

	/**
	 * The case for SouthStudent hosts to call
	 * @param host The SouthStudent caller
	 * @param params A vararg of arbitrary parameters for the visitor to use.
	 * @return the result of the running this case.
	 */
	public abstract Object southCase(SouthStudent host, Object...params);

	/**
	 * The case for NorthStudent hosts to call
	 * @param host The NorthStudent caller
	 * @param params A vararg of arbitrary parameters for the visitor to use.
	 * @return the result of the running this case.
	 */
	public abstract Object northCase(NorthStudent host, Object...params);
}

Now consider the situation where you have an algorthm that processes two unknown IStudents.    No matter what that algorithm is, you can fundamentally break the algorithm down into the handling of 4 distinct cases:

  1. The first student is a NorthStudent and the second student is a NorthStudent.
  2. The first student is a NorthStudent and the second student is a SouthStudent.
  3. The first student is a SouthStudent and the second student is a NorthStudent.
  4. The first student is a SouthStudent and the second student is a SouthStudent.

Note that these 4 cases can equivalently be presented in this hierarchal form:

  1. The first student is a NorthStudent and
    1. the second student is a NorthStudent.
    2. the second student is a SouthStudent.
  2. The first student is a SouthStudent and
    1. the second student is a NorthStudent.
    2. the second student is a SouthStudent.

In this presentation, we can clearly see that 4 cases break down into 2 cases for the first student and 2 cases for the second student.  

But this is exactly what a IStudentVisitor is, isn't it?   This means that the visitor representation of any algorithm on 2 IStudents can be represented as a single visitor for the first student.  That main visitor internally holds two sub-visitors for the second student, one for when the first student is a NorthStudent and one for when the first student is a SouthStudent..

Here's a picture of how that visitor would be applied to the 2 unknown students, showing the delegation process:

Click for full-size view
double host visitor

The code for the visitor thus looks like the following:

/**
 * This is the main visitor, "VISITOR_MAIN".   
 * The main visitor processes the first student.
 */
IStudentVisitor double_host_algo = new IStudentVisitor() {
	/**
	 * This is the case when the first student is a SouthStudent
	 * @param host1 The first student
	 * @param otherHosts otherHosts[0] is the second student
	 */
	@Override
	public Object southCase(SouthStudent host1, Object...otherHosts) {
		// At this point, we know that the first student is a SouthStudent but we still don't know the second student.
		return ((IStudent)otherHosts[0]).execute( new IStudentVisitor() {
			// This is sub-visitor for when the first student is a SouthStudent, "VISITOR_SUB_S".
			// The sub-visitor processes the second student
			
			/**
			 * This case covers the (SouthStudent, SouthStudent) case
			 * @host2 The second student
			 * @param nu Not used
			 * @return The results of processing this case
			 */
			public Object southCase(SouthStudent host2, Object...nu) {
				// Do whatever is needed to process the (SouthStudent, SouthStudent) case.
				// Both host1 and host2 are of known type and are in scope!
			}
			
			/**
			 * This case covers the (SouthStudent, NorthStudent) case
			 * @host2 The second student
			 * @param nu Not used
			 * @return The results of processing this case
			 */
			public Object northCase(NorthStudent host2, Object...nu) {
				// Do whatever is needed to process the (SouthStudent, NorthStudent) case
				// Both host1 and host2 are of known type and are in scope!
			}
		});
	}	

	/**
	 * This is the case when the first student is a NorthStudent
	 * @param host1 The first student	
	 * @param otherHosts otherHosts[0] is the second student
	 */
	@Override
	public Object northCase(NorthStudent host1, Object...otherHosts) {
		// At this point, we know that the first student is a NorthStudent but we still don't know the second student.
		return ((IStudent)otherHosts[0]).execute( new IStudentVisitor() {
			// This is sub-visitor for when the first student is a NorthStudent, "VISITOR_SUB_N".
			// The sub-visitor processes the second student
			
			/**
			 * This case covers the (NorthStudent, SouthStudent) case
			 * @host2 The second student
			 * @param nu Not used
			 * @return The results of processing this case
			 */
			public Object southCase(SouthStudent host2, Object...nu) {
				// Do whatever is needed to process the (NorthStudent, SouthStudent) case
				// Both host1 and host2 are of known type and are in scope!
			}
			
			/**
			 * This case covers the (NorthStudent, NorthStudent) case
			 * @host2 The second student
			 * @param nu Not used
			 * @return The results of processing this case
			 */
			public Object northCase(NorthStudent host2, Object...nu) {
				// Do whatever is needed to process the (NorthStudent, NorthStudent) case
				// Both host1 and host2 are of known type and are in scope!
			}
		});
	}	
};

The above visitor would be used as such:

IStudent student1, student2;   // initialization elided.  It doesn't matter what the students are.

Object result = student1.execute(double_host_algo, student2);  // This will give the correct result for any possible pair of students.

Some things to notice:


© 2018 by Stephen Wong