Java › Design Patterns in Java
How would you implement the Visitor pattern in Java to add new operations to an existing class hierarchy without modifying those classes, and how do Java's sealed classes and pattern matching (switch expressions) provide a modern alternative?
AdvancedAnswer
The classic Visitor pattern uses double-dispatch (each element class has an accept(Visitor) method calling back into a corresponding visit(ConcreteElement) method on the visitor) to let you add new operations over a fixed set of element types without modifying those types themselves, at the cost of considerable boilerplate and difficulty adding new element types later; Java's sealed classes (restricting which classes can implement/extend an interface) combined with exhaustive pattern-matching switch expressions (Java 17+/21+) provide a much more concise, compiler-checked alternative for exactly this "closed set of types, add new operations" scenario, since the compiler can verify at compile time that a switch expression handles every possible sealed subtype, without any of the classic Visitor pattern's boilerplate.
// Modern alternative using sealed interfaces and pattern matching (Java 21+)
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}
double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
// compiler ERRORS if a new Shape subtype is added and this switch isn't updated -- exhaustiveness checking
};
}Real-world example A geometry library modeling shapes as a sealed hierarchy uses exhaustive pattern-matching switch expressions to implement operations like area() and perimeter(), gaining the same 'add new operations easily' benefit the classic Visitor pattern provided, but with compiler-enforced exhaustiveness checking and dramatically less boilerplate than hand-writing accept()/visit() methods across every class.