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.© 2019 by Stephen Wong