public final class Visitor {
/** Shape
*
* We wish to define a data type with exactly two variants: Circle and Rectangle.
*
* In particular, we do not want Shape to be open to extension by
* new kinds of variants (as would be the case with an abstract class).
* The reason we do not want the variants of Shape to be open to extension
* is because this will grant us the ability to extend the operations
* that may be performed on Shapes, instead. Each such operation will be
* defined by a particular "ShapeVisitor". "ShapeVisitor" is designed in
* such a way that it exhaustively handles every variant of Shape, which
* would be impossible to do if the variants of Shape were open to extension.
*
* As such, the "Visitor pattern" allows us a design that intentionally
* trades away extensibility in variants of a data structure in exchange for
* extensibility in operations that can be performed on that data structure.
*/
public interface Shape {
public A visit(ShapeVisitor visitor);
}
/** ShapeVisitor
*
* The essense of the visitor pattern is that instead of defining each `Shape`
* operation as a method on the `Shape` class, we encode each operation we
* wish to perform on `Shape`s as a `ShapeVisitor`.
*
* `ShapeVisitor` has one method for each `Shape` variant. The number of
* variants is fixed (as opposed to making `Shape` an abstract class, where
* the number of variants would be extensible) but in exchange for that, the
* operations (encoded as `ShapeVisitor`s as opposed to as methods on `Shape`)
* is extensible.
*/
public interface ShapeVisitor {
A visitCircle(Double x, Double y, Double r);
A visitRectangle(Double x, Double y, Double w, Double h);
}
public static final Shape newCircle(Double x, Double y, Double r) {
return new Shape() {
public A visit(ShapeVisitor visitor) {
return visitor.visitCircle(x, y, r);
}
};
}
public static final Shape newRectangle(Double x, Double y, Double w, Double h) {
return new Shape() {
public A visit(ShapeVisitor visitor) {
return visitor.visitRectangle(x, y, w, h);
}
};
}
public static final Shape exampleCircle =
newCircle(2.0, 1.4, 4.5);
public static final Shape exampleRectangle =
newRectangle(1.3, 3.1, 10.3, 7.7);
/** Defining a `ShapeVisitor` is analogous to pattern matching on the
* variants of `Shape` in a pattern-matching language.
*/
public static final ShapeVisitor area =
new ShapeVisitor() {
public Double visitCircle(Double x, Double y, Double r) {
return 2 * Math.PI * Math.pow(r, 2);
}
public Double visitRectangle(Double x, Double y, Double w, Double h) {
return w * h;
}
};
/** We can, and even downstream clients can, define new operations on all
* of `Shape`s variants by creating new `ShapeVisitor`s.
*/
public static final ShapeVisitor translate(Double dx, Double dy) {
return new ShapeVisitor() {
public Shape visitCircle(Double x, Double y, Double r) {
return newCircle(x + dx, y + dy, r);
}
public Shape visitRectangle(Double x, Double y, Double w, Double h) {
return newRectangle(x + dx, y + dx, w, h);
}
};
}
public static final ShapeVisitor show =
new ShapeVisitor() {
public String visitCircle(Double x, Double y, Double r) {
return String.format("Circle {x = %f, y = %f, r = %f}", x, y, r);
}
public String visitRectangle(Double x, Double y, Double w, Double h) {
return String.format("Rectangle {x = %f, y = %f, w = %f, h = %f}", x, y, w, h);
}
};
public static final void main(String[] args) {
String circleString =
exampleCircle.visit(translate(3.0, 4.0)).visit(show);
String rectangleString =
exampleRectangle.visit(translate(-4.0, -3.0)).visit(show);
System.out.println(circleString);
System.out.println(rectangleString);
}
}