Java Articles

Advertisement

Visitor Design Pattern

by Ram Satish

Share

Visitor Pattern allows adding new methods to existing hierarchies without modifying the structure. One can dynamically add new behavior to the class. Decorator Pattern is another pattern in this family.

Visitor Pattern Example

Our example deals with a student enrolling to a painting course. There are number of courses available, like Pastel Course, Oil painting course and water color course. Enrollment Manager enrolls a student to a painting course. There are now few rules which governs the limit and cost of the course.

Rues are:

Advertisement
http://hatha-yoga-poses.blogspot.in/

Here is my Course interface:

Course

package patterns.visitor;

public interface Course {
void join(Student s);
Student[] getStudents();
int getLimit();
int getCost();
void incrementLimit(int additional);
}

Here is the code for OilCourse. Note other courses will be in similar lines so I am not pasting the code here.

OilCourse

package patterns.visitor;

import java.util.HashSet;
import java.util.Set;

public class OilCourse implements Course {
private Set students = new HashSet();
private int limit;

public OilCourse() {
limit = 15;
}

@Override
public void join(Student s) {
students.add(s);
}

@Override
public Student[] getStudents() {
return students.toArray(new Student[students.size()]);
}

@Override
public int getLimit() {
return limit;
}

@Override
public int getCost() {
return 2000;
}

@Override
public void incrementLimit(int i) {
limit += i;
}
}

CourseCostAduster is the class which adjusts the cost per course based on the various rules.

package patterns.visitor;

public class CourseCostAdjuster {
void adjustCost(Course inCourse, Student inStudent) {
if (inCourse instanceof OilCourse) {
if (inCourse.getLimit() < 10) {
inStudent.setCost(inCourse.getCost() - inCourse.getCost()*10/100);
}
}
if (inCourse instanceof WatercolorCourse) {
if (inStudent.ownMaterial()) {
inStudent.setCost(inCourse.getCost() - 200);
}
}
}
}

LimitAdjuster adjusts the upper cap of the class.

package patterns.visitor;

public class LimitAdjuster {
void adjustLimit(Course inCourse) {
if (inCourse instanceof PastelCourse) {
if (inCourse.getStudents() != null && inCourse.getStudents().length == inCourse.getLimit()) {
inCourse.incrementLimit(inCourse.getLimit() + 5);
}
}
}
}

LimitValidator validates whether the limit is violated.

package patterns.visitor;

public class LimitValidator {
void validateLimit(Course inCourse) {
if (inCourse.getStudents() != null && inCourse.getStudents().length == inCourse.getLimit()) {
throw new RuntimeException("Limit reached, can't enrol anymore");
}
if (inCourse instanceof PastelCourse) {
if (inCourse.getStudents() != null && inCourse.getStudents().length == 40) {
throw new RuntimeException("Limit reached, can't enrol anymore");
}
}
}
}

EnrolmentManager does the job enrolling a student. It adjusts the cost and adjusts the upper limit if needed.

package patterns.visitor;

public class EnrolmentManager {
public void enrol(Student inStudent, Course inCourse) {
new CourseCostAdjuster().adjustCost(inCourse, inStudent);
new LimitAdjuster().adjustLimit(inCourse);
inCourse.join(inStudent);
}
}

The main problem with the above design is we have rules which keeps coming and for every rule we need to do the type check on course to do the course specific logic. There is a dispatching involved which we are managing ourselves and not using the power of polymorphic.

http://hatha-yoga-poses.blogspot.in/

We will now use Visitor pattern to dynamically dispatch the logic to the right rule validating classes.

All the rules have one pattern common in them and that is, the dispatching logic (if pastel then…). Based on this pattern, we come up with the below CourseVisitor interface
Mbr/>

package patterns.visitor;

public interface CourseVisitor {
void visit(OilCourse oilCourse, Student student);

void visit(PastelCourse pastelCourse, Student student);

void visit(WatercolorCourse watercolorCourse, Student student);
}

Each rule will now implement the above visitor interface.

CostAdjuster

package patterns.visitor;

public class CostAdjusterVisitor implements CourseVisitor {

@Override
public void visit(OilCourse oilCourse, Student student) {
if (oilCourse.getLimit() < 10) {
student.setCost(oilCourse.getCost() - oilCourse.getCost()*10/100);
}
}

@Override
public void visit(PastelCourse pastelCourse, Student student) {
}

@Override
public void visit(WatercolorCourse watercolorCourse, Student student) {
if (student.ownMaterial()) {
student.setCost(watercolorCourse.getCost() - 200);
}
}
}

LimitAdjusterVisitor

package patterns.visitor;

public class LimitAdjusterVisitor implements CourseVisitor {
@Override
public void visit(OilCourse oilCourse, Student student) {
}

@Override
public void visit(PastelCourse pastelCourse, Student student) {
if (pastelCourse.getStudents() != null && pastelCourse.getStudents().length == pastelCourse.getLimit()) {
pastelCourse.incrementLimit(pastelCourse.getLimit() + 5);
}
}

@Override
public void visit(WatercolorCourse watercolorCourse, Student student) {
}
}

LimitValidatorVisitor

package patterns.visitor;

public class LimitValidatorVisitor implements CourseVisitor {
@Override
public void visit(OilCourse oilCourse, Student student) {
validateLimit(oilCourse);
}

@Override
public void visit(PastelCourse pastelCourse, Student student) {
if (pastelCourse.getStudents() != null && pastelCourse.getStudents().length == 40) {
throw new RuntimeException("Limit reached, can't enrol anymore");
}
}

@Override
public void visit(WatercolorCourse watercolorCourse, Student student) {
validateLimit(watercolorCourse);
}

private void validateLimit(Course course) {
if (course.getStudents() != null && course.getStudents().length == course.getLimit()) {
course.incrementLimit(course.getLimit() + 5);
}
}
}

With this change we now have all rules in proper methods. In a way, we are partially successful in dissolving the ‘if instanceof PastelCourse’ and else dispatching pattern. Now our next task is to apply the rule to the course.

We will now introduce a new method to Course interface to accept the rule.

package patterns.visitor;

public interface Course {
...
void accept(CourseVisitor visitor, Student student);
}

The method accept(visitor) will simply pass itself to the visitor object. Due to polymorphism, the control will automatically get dispatched to the right visitor class.

package patterns.visitor;

import java.util.HashSet;
import java.util.Set;

public class OilCourse implements Course {
...
@Override
public void accept(CourseVisitor visitor, Student student) {
visitor.visit(this, student);
}
}

The visit method as you know is overloaded and there are multiple versions available in Course interface. Again due to polymorphism, the control will get dispatched to the right visit method. Thus in visitor pattern, there is dual dispatch, first the visitor and then the visited.

http://hatha-yoga-poses.blogspot.in/

Finally, the EnrolmentManager will look like below:

package patterns.visitor;

public class EnrolmentManager {
public void enrol(Student inStudent, Course inCourse) {
inCourse.accept(new CostAdjusterVisitor(), inStudent);
inCourse.accept(new LimitAdjusterVisitor(), inStudent);
inCourse.accept(new LimitValidatorVisitor(), inStudent);
inCourse.join(inStudent);
}
}

Share

Advertisement

Related

Advertisement

Latest

Advertisement