Problem
Say we have a Shape class
class Shape {}
And also several different classes that extends Shape
class Triangle extends Shape {}
class Square extends Shape {}
class Circle extends Shape {}
/* several more */
Problem
- There is a requirement to execute Task A for each shape, where each shape has its own distinct implementation of Task A.
- There could be future requirements to execute Task B, C, D, etc for each shape, where each shape has its own distinct implementation of Task B, C, D, etc
- As developers, we do not want to add the implementation code of a task for each shape class as it would bloat over time
Solution
Define a ShapeVistor interface
interface ShapeVisitor {
void visitTriangle(Triangle triangle);
void visitSquare(Square square);
void visitCircle(Circle circle);
/* several more */
}
Create a class implementing ShapeVisitor to handle the implementation of Task A for each shape
class TaskAShapeVisitor implements ShapeVisitor {
void visitTriangle(Triangle triangle) { sout("Triangle implementation Task A"); }
void visitSquare(Square square) { sout("Square implementation Task A"); }
void visitCircle(Circle circle) { sout("Circle implementation Task A"); }
/* several more */
}
Next, either define a new interface (or add accept method in the Shape class)
interface AcceptShapeVisitor {
void accept(ShapeVistor visitor);
}
Now modify the shape classes
class Triangle extends Shape implements AcceptShapeVisitor {
public void accept(ShapeVistor visitor) { visitor.visitTriangle(this); }
}
class Square extends Shape implements AcceptShapeVisitor {
public void accept(ShapeVistor visitor) { visitor.visitSquare(this); }
}
class Circle extends Shape implements AcceptShapeVisitor {
public void accept(ShapeVistor visitor) { visitor.visitCircle(this); }
}
/* several more */
You are done! Using it will look something like this:
public static void main(String[] args) {
List<AcceptShapeVisitor> shapes = List.of(new Triangle(), new Square(), new Circle(), etc);
TaskAShapeVisitor taskAVisitor = new TaskAVisitor();
for (AcceptShapeVisitor shape : shapes) {
shape.accept(taskAVisitor);
}
// OUTPUT
// Triangle implementation Task A
// Square implementation Task A
// Circle implementation Task A
// etc
}
Future requirements for Task B will only require the creation of the following class:
class TaskBShapeVisitor implements ShapeVisitor {
void visitTriangle(Triangle triangle) { sout("Triangle implementation Task B"); }
void visitSquare(Square square) { sout("Square implementation Task B"); }
void visitRectangle(Rectangle rectangle) { sout("Rectangle implementation Task B"); }
void visitCircle(Circle circle) { sout("Circle implementation Task B"); }
void visitEclipse(Eclipse eclipse) { sout("Eclipse implementation Task B"); }
/* several more */
}
public static void main(String[] args) {
List<AcceptShapeVisitor> shapes = List.of(new Triangle(), new Square(), new Circle(), etc);
TaskBShapeVisitor taskBVisitor = new TaskBVisitor();
for (AcceptShapeVisitor shape : shapes) {
shape.accept(taskBVisitor);
}
// OUTPUT
// Triangle implementation Task B
// Square implementation Task B
// Circle implementation Task B
// etc
}
Why This Will Not Work
class Shape {}
class Triangle extends Shape {}
class Square extends Shape {}
class Circle extends Shape {}
interface Task {
void visitTriangle(Triangle triangle);
void visitSquare(Square square);
void visitCircle(Circle circle);
/* several more */
}
class TaskA implements Task {
void do(Triangle triangle) { sout("Triangle implementation Task A"); }
void do(Square square) { sout("Square implementation Task A"); }
void do(Circle circle) { sout("Circle implementation Task A"); }
/* several more */
}
public static void main(String[] args) {
List<Shape> shapes = List.of(new Triangle(), new Square(), new Circle());
TaskA taskA = new TaskA();
for (Shape shape : shapes) {
taskA.do(shape); // <--- THIS WOULD NOT COMPILE
// have to do something tedious like this
if (shape instanceof Triangle) taskA.do((Triangle) shape);
else if (shape instanceof Square) taskA.do((Square) shape);
else if (shape instanceof Circle) taskA.do((Circle) shape);
}
}