The Visitor Pattern is a behavioural design pattern that allows adding new operations to existing object structures without modifying their classes. It achieves this by separating the algorithm from the objects on which it operates.

The Visitor pattern is especially useful when dealing with object structures made up of classes with different interfaces and when new operations on these classes are frequently added.

Applicability

  • When you have a stable set of classes but need to perform unrelated operations on them.
  • When the object structure is stable, but the operations on them frequently change.
  • When you want to add operations without modifying the element classes.

Pros and Cons

Advantages

  • Adds new operations without changing object structure
    • You can add new functionality by introducing a new visitor class, avoiding modifying the existing element classes.
  • Promotes open/closed principle
    • The classes of the elements remain closed for modification but open for extension via visitors.
  • Cleans up element classes
    • Element classes are not cluttered with unrelated operations.

Drawbacks

  • Adding new element classes is hard
    • If you add a new element class, all existing visitor classes must be updated.
  • Breaks encapsulation
    • The visitor has to know the internal structure of elements to perform operations, which may expose internal details.
  • Complex with deeply nested structures
    • It may increase complexity if object structures are deeply nested or contain many types.

Approach

  1. Define an Element interface with an accept(Visitor visitor) method.
  2. Implement ConcreteElement classes that implement the accept() method and call the appropriate method on the visitor.
  3. Define a Visitor interface with visit methods for each type of element.
  4. Implement ConcreteVisitor classes that implement the visitor interface and define operations to perform on elements.
  5. The Client creates elements and visitors and initiates visiting through accept().

Components

  • Element (interface)
    • Declares accept(Visitor v) method.
  • ConcreteElement
    • Implements accept() and calls visitor.visit(this).
  • Visitor (interface)
    • Declares a visit() method for each concrete element type.
  • ConcreteVisitor
    • Implements the logic for each visit operation.
classDiagram
    class Visitor {
        <<abstract>>
        +visit_element_a(element)
        +visit_element_b(element)
    }

    class ConcreteVisitor {
        +visit_element_a(element)
        +visit_element_b(element)
    }

    class Element {
        <<abstract>>
        +accept(visitor)
    }

    class ConcreteElementA {
        +accept(visitor)
    }

    class ConcreteElementB {
        +accept(visitor)
    }

    class ObjectStructure {
        -elements: List[Element]
        +attach(element: Element)
        +accept(visitor: Visitor)
    }

    Visitor <|-- ConcreteVisitor
    Element <|-- ConcreteElementA
    Element <|-- ConcreteElementB
    ConcreteElementA --> Visitor : uses
    ConcreteElementB --> Visitor : uses
    ObjectStructure --> Element : contains

Example

Let’s use an example of a Document with different Elements like Paragraph and Image. We want to apply multiple operations like rendering and exporting without modifying the element classes.

Element

interface DocumentElement {
    void accept(DocumentVisitor visitor);
}

Concrete element

class Paragraph implements DocumentElement {
    private String text;
 
    public Paragraph(String text) {
        this.text = text;
    }
 
    public String getText() {
        return text;
    }
 
    public void accept(DocumentVisitor visitor) {
        visitor.visit(this);
    }
}
class Image implements DocumentElement {
    private String filename;
 
    public Image(String filename) {
        this.filename = filename;
    }
 
    public String getFilename() {
        return filename;
    }
 
    public void accept(DocumentVisitor visitor) {
        visitor.visit(this);
    }
}

Visitor

interface DocumentVisitor {
    void visit(Paragraph paragraph);
    void visit(Image image);
}

Concrete visitor

class RenderVisitor implements DocumentVisitor {
    public void visit(Paragraph paragraph) {
        System.out.println("Rendering paragraph: " + paragraph.getText());
    }
 
    public void visit(Image image) {
        System.out.println("Rendering image: " + image.getFilename());
    }
}
 
class ExportVisitor implements DocumentVisitor {
    public void visit(Paragraph paragraph) {
        System.out.println("Exporting paragraph to HTML: <p>" + paragraph.getText() + "</p>");
    }
 
    public void visit(Image image) {
        System.out.println("Exporting image to HTML: <img src='" + image.getFilename() + "' />");
    }
}

Client

public class VisitorExample {
    public static void main(String[] args) {
        DocumentElement[] elements = {
            new Paragraph("Hello, Visitor Pattern!"),
            new Image("diagram.png")
        };
 
        DocumentVisitor render = new RenderVisitor();
        DocumentVisitor export = new ExportVisitor();
 
        System.out.println("--- Rendering ---");
        for (DocumentElement e : elements) {
            e.accept(render);
        }
 
        System.out.println("\n--- Exporting ---");
        for (DocumentElement e : elements) {
            e.accept(export);
        }
    }
}
--- Rendering ---
Rendering paragraph: Hello, Visitor Pattern!
Rendering image: diagram.png

--- Exporting ---
Exporting paragraph to HTML: <p>Hello, Visitor Pattern!</p>
Exporting image to HTML: <img src='diagram.png' />

Back to parent page: Behavioural Patterns

Design_Pattern Behavioural_Design_Patterns Visitor_Pattern SOFT3202