Add new operations over a set of classes without modifying those classes, by moving the operation into a separate visitor.
A set of shape classes (Circle, Square, Triangle) that need `calculateArea()`, then later `exportToSVG()`, then later `toJSON()` — each new operation means opening every shape class again and adding a method, even though the shapes themselves haven't changed.
Each element implements one `accept(visitor)` method that calls back into the visitor with itself (`visitor.visitCircle(this)`). New operations are new visitor classes — the element classes never change again after `accept` is added once.
Adding `ExportVisitor` later needs zero changes to `Circle` or `Square` — only a new visitor class.
class Circle {
constructor(radius) { this.radius = radius; }
accept(visitor) { return visitor.visitCircle(this); }
}
class Square {
constructor(side) { this.side = side; }
accept(visitor) { return visitor.visitSquare(this); }
}
const AreaVisitor = {
visitCircle: (c) => Math.PI * c.radius ** 2,
visitSquare: (s) => s.side ** 2,
};
const ExportVisitor = {
visitCircle: (c) => `<circle r="${c.radius}"/>`,
visitSquare: (s) => `<rect width="${s.side}" height="${s.side}"/>`,
};
const shapes = [new Circle(3), new Square(4)];
console.log(shapes.map((s) => s.accept(AreaVisitor)));
console.log(shapes.map((s) => s.accept(ExportVisitor)));