Visitor

Design patterns / Behavioral patterns
image

#include <vector>
#include <iostream>
#include <memory>
using namespace std;

class Engine;
class Wheel;
class Car;

//Visitor
class CarVisitor {
public:
    virtual ~CarVisitor() = default;
    virtual void visit(Engine* engine) = 0;
    virtual void visit(Wheel* wheel) = 0;
    virtual void visit(Car* car) = 0;
};

class Element {
public:
    virtual ~Element() = default;
    virtual void accept(CarVisitor* v) = 0;
};

//ConcreteElement
class Engine: public Element {
public:
    void accept(CarVisitor* v) override {
        v->visit(this);
    }
};

//ConcreteElement
class Wheel: public Element {
private:
    int number;

public:
    explicit Wheel(int number)
        : number(number) {}

    int getNumber() {
        return number;
    }

    void accept(CarVisitor* v) override {
        v->visit(this);
    }
};

//ConcreteElement
class Car: public Element {
private:
    vector<unique_ptr<Element>> items;

public:
    Car() {
        items.push_back(make_unique<Engine>());
        for (int i = 1; i <= 4; i++) {
            items.push_back(make_unique<Wheel>(i));
        }
    }

    void accept(CarVisitor* v) override {
        for (const unique_ptr<Element>& e : items) {
            e->accept(v);
        }
        v->visit(this);
    }
};

//ConcreteVisitor
class TestCarVisitor: public CarVisitor {
public:
    void visit(Engine*) override {
        cout << "test engine\n";
    }

    void visit(Wheel* wheel) override {
        cout << "test wheel #" <<
        wheel->getNumber() << "\n";
    }

    void visit(Car*) override {
        cout << "test car\n";
    }
};

//ConcreteVisitor
class RepairCarVisitor: public CarVisitor {
public:
    void visit(Engine*) override {
        cout << "repair engine\n";
    }

    void visit(Wheel* wheel) override {
        cout << "repair wheel #" <<
        wheel->getNumber() << "\n";
    }

    void visit(Car*) override {
        cout << "repair car\n";
    }
};

//Client
Car car;
TestCarVisitor v1;
RepairCarVisitor v2;

car.accept(&v1);
//printed: test engine
//         test wheel #1
//         test wheel #2
//         test wheel #3
//         test wheel #4
//         test car
car.accept(&v2);
//printed: repair engine
//         repair wheel #1
//         repair wheel #2
//         repair wheel #3
//         repair wheel #4
//         repair car


Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
Read more

The problem. A document is made of paragraphs, images and tables. The program must export it, count its words and check its spelling. If each operation is added as a method to every kind of part, the part classes grow with every new operation. The code of one operation is also spread over many classes.

The idea. Each operation becomes a separate object, called a visitor. A visitor has one method for each kind of element. Each element has one method that takes a visitor and calls the visitor's method for its own kind. So the right code runs for each pair of element and operation, without checks of the type. To add a new operation, you write a new visitor; the element classes do not change.

How to read the example. The elements are of several kinds. Each concrete visitor is one operation, with a separate method for each kind of element. The client code gives a visitor to the elements, and each element sends it to the method for its own kind. In most examples one element holds the others and gives the visitor to each of its parts. Two visitors then pass over the same parts, each with its own operation. In some languages the elements are plain data with a type tag, and a visitor is one function with a version for each kind.

When to use it. Use Visitor when the set of element classes is stable, but new operations on them appear often. Do not use it when new kinds of element are added often: each new kind needs a new method in every visitor.

A common mistake. Checking the type of the element with conditions inside one general visitor method. A new kind of element is then missed without any warning. Give each kind its own method.