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
- Define an Element interface with an
accept(Visitor visitor)
method. - Implement ConcreteElement classes that implement the
accept()
method and call the appropriate method on the visitor. - Define a Visitor interface with visit methods for each type of element.
- Implement ConcreteVisitor classes that implement the visitor interface and define operations to perform on elements.
- The Client creates elements and visitors and initiates visiting through
accept()
.
Components
- Element (interface)
- Declares
accept(Visitor v)
method.
- Declares
- ConcreteElement
- Implements
accept()
and callsvisitor.visit(this)
.
- Implements
- Visitor (interface)
- Declares a
visit()
method for each concrete element type.
- Declares a
- 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 Element
s 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