C++ Visitor Design Pattern Example

1. Definition

The Visitor Design Pattern represents an operation to be performed on elements of an object structure. It allows you to add further operations without modifying the classes of the objects on which they operate.

2. Problem Statement

Imagine having a complex object structure, like a composite pattern. Over time, you might need to perform new operations on all the elements of the structure, but you don't want to keep changing the existing classes, breaking the Open/Closed principle.

3. Solution

Introduce a visitor class that implements an operation to be performed on elements of the object structure. The classes of these objects can accept a visitor and let it operate on them, separating their data from the behavior.

4. Real-World Use Cases

1. A document editor where various elements (like text, images, tables) need different operations (e.g., rendering, exporting).

2. Tax calculations where different products have different tax rules.

5. Implementation Steps

1. Declare a visitor interface with a visit method for each type of concrete element in the object structure.

2. Concrete elements must offer an accept method that takes a visitor as an argument.

3. Create concrete visitor classes for each operation to be implemented.

4. The client creates concrete visitors and passes them into objects using the accept method.

6. Implementation in C++

// Forward declaration
class ConcreteElementA;
class ConcreteElementB;
// Visitor Base Class
class Visitor {
public:
    virtual void visitConcreteElementA(ConcreteElementA* element) = 0;
    virtual void visitConcreteElementB(ConcreteElementB* element) = 0;
};
// Element Base Class
class Element {
public:
    virtual void accept(Visitor* visitor) = 0;
};
// Concrete Elements
class ConcreteElementA : public Element {
public:
    void accept(Visitor* visitor) override {
        visitor->visitConcreteElementA(this);
    }
};
class ConcreteElementB : public Element {
public:
    void accept(Visitor* visitor) override {
        visitor->visitConcreteElementB(this);
    }
};
// Concrete Visitor
class ConcreteVisitor1 : public Visitor {
public:
    void visitConcreteElementA(ConcreteElementA* element) override {
        std::cout << "Visitor 1: Visited Element A" << std::endl;
    }
    void visitConcreteElementB(ConcreteElementB* element) override {
        std::cout << "Visitor 1: Visited Element B" << std::endl;
    }
};
// Example Usage
int main() {
    Element* elements[] = {new ConcreteElementA(), new ConcreteElementB()};
    Visitor* visitor = new ConcreteVisitor1();
    for (Element* e : elements) {
        e->accept(visitor);
    }
    delete visitor;
    for (Element* e : elements) {
        delete e;
    }
    return 0;
}

Output:

Visitor 1: Visited Element A
Visitor 1: Visited Element B

Explanation:

1. A base Visitor interface is declared that contains a visit method for each concrete element class.

2. Each concrete element class implements an accept method that takes a visitor and directs the visitor to the visit method that matches its class.

3. The ConcreteVisitor1 class provides specific implementations of the visit methods.

4. In the example, we created instances of both elements and passed a visitor to them using the accept method.

7. When to use?

Use the Visitor Pattern when:

1. You need to perform operations across a disparate set of objects but don't want to pollute their classes with these operations.

2. New operations are needed frequently, and the object structure remains relatively unchanged.

3. You want to keep related operations together by defining them in one class.

4. You need to break apart monolithic structures, making them easier to understand and modify.


Comments