- 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.
- The classic technique for recovering lost type information.
- Do the right thing based on the type of two objects.
- Double dispatch
Problem
- Many distinct and unrelated operations need to be performed on node objects in a heterogeneous aggregate structure. You want to avoid “polluting” the node classes with these operations. And, you don’t want to have to query the type of each node and cast the pointer to the correct type before performing the desired operation.
Discussion
Visitor’s primary purpose is to abstract functionality that can be applied to an aggregate hierarchy of “element” objects. The approach encourages designing lightweight Element classes - because processing functionality is removed from their list of responsibilities. New functionality can easily be added to the original inheritance hierarchy by creating a new Visitor subclass.
Visitor implements “double dispatch”. OO messages routinely manifest “single dispatch” - the operation that is executed depends on: the name of the request, and the type of the receiver. In “double dispatch”, the operation executed depends on: the name of the request, and the type of TWO receivers (the type of the Visitor and the type of the element it visits).
The implementation proceeds as follows. Create a Visitor class hierarchy that defines a pure virtual visit() method in the abstract base class for each concrete derived class in the aggregate node hierarchy. Each visit() method accepts a single argument - a pointer or reference to an original Element derived class.
Each operation to be supported is modelled with a concrete derived class of the Visitor hierarchy. The visit() methods declared in the Visitor base class are now defined in each derived subclass by allocating the “type query and cast” code in the original implementation to the appropriate overloaded visit() method.
Add a single pure virtual accept() method to the base class of the Element hierarchy. accept() is defined to receive a single argument - a pointer or reference to the abstract base class of the Visitor hierarchy.
Each concrete derived class of the Element hierarchy implements the accept() method by simply calling the visit() method on the concrete derived instance of the Visitor hierarchy that it was passed, passing its “this” pointer as the sole argument.
Everything for “elements” and “visitors” is now set-up. When the client needs an operation to be performed, (s)he creates an instance of the Vistor object, calls the accept() method on each Element object, and passes the Visitor object.
The accept() method causes flow of control to find the correct Element subclass. Then when the visit() method is invoked, flow of control is vectored to the correct Visitor subclass. accept() dispatch plus visit() dispatch equals double dispatch.
The Visitor pattern makes adding new operations (or utilities) easy - simply add a new Visitor derived class.
Following is the code for visitor design pattern.
#include<iostream>
#include<string>
#include<list>
using namespace std;
class Wheel;
class Engine;
class Body;
class Car;
class CarElementVisitor {
public:
virtual void visit(Wheel* wheel) = 0;
virtual void visit(Engine* engine) = 0;
virtual void visit(Body* body) = 0;
virtual void visit(Car* car) = 0;
virtual ~CarElementVisitor() {}
};
class CarElement {
public:
virtual void accept(CarElementVisitor* visitor) = 0; // CarElements have to provide accept().
virtual ~CarElement(){}
};
class Wheel : public CarElement {
private:
std::string name;
public:
Wheel()
{
std::cout<<"C Wheel\n";
}
Wheel(std::string name) {
this->name = name;
std::cout<<"C Wheel\n";
}
virtual ~Wheel()
{
std::cout<<"D Wheel\n";
}
std::string getName() {
return this->name;
}
void accept(CarElementVisitor* visitor) {
/*
* accept(CarElementVisitor) in Wheel implements
* accept(CarElementVisitor) in CarElement, so the call
* to accept is bound at run time. This can be considered
* the first dispatch. However, the decision to call
* visit(Wheel) (as opposed to visit(Engine) etc.) can be
* made during compile time since 'this' is known at compile
* time to be a Wheel. Moreover, each implementation of
* CarElementVisitor implements the visit(Wheel), which is
* another decision that is made at run time. This can be
* considered the second dispatch.
*/
visitor->visit(this);
}
};
class Engine : public CarElement {
public:
void accept(CarElementVisitor* visitor) {
visitor->visit(this);
}
Engine()
{
std::cout<<"C Engine\n";
}
virtual ~Engine()
{
std::cout<<"D Engine\n";
}
};
class Body : public CarElement {
public:
void accept(CarElementVisitor* visitor) {
visitor->visit(this);
}
Body()
{
std::cout<<"C Body\n";
}
virtual ~Body()
{
std::cout<<"D Body\n";
}
};
class Car : public CarElement {
private:
std::list<CarElement*> elements;
public:
Car() {
std::cout<< "C Car\n";
//create new Array of elements
elements.push_back(new Wheel("front left"));
elements.push_back(new Wheel("front right"));
elements.push_back(new Wheel("back left"));
elements.push_back(new Wheel("back right"));
elements.push_back(new Body());
elements.push_back(new Engine());
}
virtual ~Car()
{
for(std::list<CarElement*>::iterator it = elements.begin(); it != elements.end(); it++)
{
delete *it;
}
std::cout<<"D Car\n";
}
void accept(CarElementVisitor* visitor) {
for(std::list<CarElement*>::iterator iter = elements.begin();
iter != elements.end(); iter++)
{
(*iter)->accept(visitor);
}
visitor->visit(this);
}
};
class CarElementPrintVisitor : public CarElementVisitor {
public:
CarElementPrintVisitor()
{
std::cout<<"C CarElementPrintVisitor\n";
}
~CarElementPrintVisitor()
{
std::cout<<"D CarElementPrintVisitor\n";
}
void visit(Wheel* wheel) {
std::cout << "Visiting " << wheel->getName() << " wheel\n";
}
void visit(Engine* engine) {
std::cout << "Visiting engine\n";
}
void visit(Body* body) {
std::cout << "Visiting body\n";
}
void visit(Car* car) {
std::cout << "Visiting car\n";
}
};
class CarElementDoVisitor : public CarElementVisitor {
public:
CarElementDoVisitor()
{
cout<<"C CarElementDoVisitor\n";
}
~CarElementDoVisitor()
{
cout<<"D CarElementDoVisitor\n";
}
void visit(Wheel* wheel) {
std::cout << "Kicking my " << wheel->getName() << " wheel\n";
}
void visit(Engine* engine) {
std::cout << "Starting my engine\n";
}
void visit(Body* body) {
std::cout << "Moving my body\n";
}
void visit(Car* car) {
std::cout << "Starting my car\n";
}
};
int main()
{
CarElement* car = new Car();
CarElementVisitor* v1 = new CarElementPrintVisitor();
CarElementVisitor* v2 = new CarElementDoVisitor();
car->accept(v1);
car->accept(v2);
delete v1;
delete v2;
delete car;
return 0;
}