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); } }