COMP 310
|
Using a Visitor to Process Two Unknown Hosts |
|
|
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:
NorthStudent and the second student
is a NorthStudent.NorthStudent and the second student
is a SouthStudent.SouthStudent and the second student
is a NorthStudent.SouthStudent and the second student
is a SouthStudent.Note that these 4 cases can equivalently be presented in this hierarchal form:
NorthStudent and NorthStudent.SouthStudent.SouthStudent and NorthStudent.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:
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:
if statements that control which of the 4
cases is run. The visitor simply states the the cases it offers
up as services -- this is an example of declarative programming.if-else structure above. Essentially, all that has
been done is to replace if-else clauses with delegations.© 2017 by Stephen Wong