0 XP · 0 scenes

Visitor

Add new operations to a set of classes without editing them
The problem

Rectangle and Square each have area(), perimeter() and now to_json().

The idea

Each shape has one method, accept(visitor), that calls the visitor method for its own type (visit_rectangle or visit_square).

Reach for it when

The classes are stable but you keep adding operations

1

The picture from life

A tax inspector visits different kinds of shops: a grocery, a jeweller, a restaurant. The shops do not know tax rules. They just say "come in" and the inspector applies the rule for that kind of shop. Next year a new inspector (fire safety) visits the same shops with a different checklist, and no shop is rebuilt.

2

The problem

What goes wrong when you do not use it.

Rectangle and Square each have area(), perimeter() and now to_json(). Every new operation (draw, export, scale) means opening every shape class, and the shape classes fill up with unrelated code.

✗ Before
Rectanglearea()perimeter()+ every new operationSquarearea()perimeter()+ every new operation
Every new operation (area, perimeter, export...) means opening and editing every shape class.
Reach for it when
  • The classes are stable but you keep adding operations
  • Operations need different code for each class
  • You want to keep unrelated operations out of the classes
3

The idea

How the pattern fixes it.

Each shape has one method, accept(visitor), that calls the visitor method for its own type (visit_rectangle or visit_square). Each operation is a visitor class holding the code for all shapes. New operation, new visitor, no shape edited.

✓ After
«interface»Shapeaccept(visitor)RectangleSquare«interface»Visitorvisit_rectangle(r)visit_square(s)AreaVisitorPerimeterVisitor← new, no shape editedaccept(v)
  • implements
  • calls / uses
A new operation is a new Visitor class. The shapes only learn one method, accept().

Who is who

Shape (element)
accept(visitor): calls the right visit_ method.
Rectangle, Square
Concrete elements with their data.
AreaVisitor, PerimeterVisitor
One class per operation with a method per shape.

Build it in steps

  1. 1Give each element an accept(visitor).
  2. 2Declare visit_ methods, one per element type.
  3. 3Write a visitor class per operation.
  4. 4Walk the elements and call accept().
4

Watch it run

Step through the calls. Tap an arrow to jump to it.

ClientRectangleSquareAreaVisitor1accept(area)2visit_rectangle(self)3124125accept(area)6visit_square(self)7989
Step 1/8

Hand the visitor to the rectangle.

accept(area)
5

The code

Python and Java. Try writing the classes yourself from the diagram first.

class Rectangle:
    def __init__(self, width, height):
        self.width, self.height = width, height

    def accept(self, visitor):
        return visitor.visit_rectangle(self)          # tells the visitor "I am a rectangle"


class Square:
    def __init__(self, side):
        self.side = side

    def accept(self, visitor):
        return visitor.visit_square(self)


class AreaVisitor:
    def visit_rectangle(self, rectangle):
        return rectangle.width * rectangle.height

    def visit_square(self, square):
        return square.side * square.side


class PerimeterVisitor:                               # a new operation, no shape edited
    def visit_rectangle(self, rectangle):
        return 2 * (rectangle.width + rectangle.height)

    def visit_square(self, square):
        return 4 * square.side


def demo():
    shapes = [Rectangle(3, 4), Square(3)]
    return [s.accept(AreaVisitor()) for s in shapes], [s.accept(PerimeterVisitor()) for s in shapes]

What you gain

  • New operations without touching the classes
  • Related code for one operation sits together

What it costs

  • Adding a new element class means editing every visitor
  • Elements must expose enough data for visitors

You have already met it in

  • Compilers walking a syntax tree (type checking, code generation)
  • Java's FileVisitor for walking directories
  • Python's ast.NodeVisitor

Often mixed up with

  • IteratorIterator decides how to walk; Visitor decides what to do at each stop.
  • InterpreterInterpreter keeps interpret() inside each node; Visitor moves operations out into separate classes.
6

Make it stick

Model Circle and Triangle, then write AreaVisitor and a JsonVisitor. Add a third operation without editing the shapes.

Confidence