Visitor Design Pattern explained with simple example

The visitor design pattern allows a programmer to separate algorithms of the module from the object or class. This will allow to modify algorithm by adding or removing new functionalities without altering objects structure. This design pattern basically allows us to add a functionality to every element without altering their structure.

Before going ahead have a look at Design pattern simplified version.

Generic model class diagram of Visitor design pattern is as shown in image.

Visitor design pattern class diagram

In this design pattern, every element class has a member function “accept (visitor)” which acts as a dispatcher and dispatches the call to respective objected. This is called “Double Dispatch” as identification of exact function call is dependent of run time type identification of two objects. This allow us to add new functionality on a set of objects without altering their structure.

Visitor Design Pattern Code Example:

Let’s take an example of numeric expressions, in which an mathematical expression will be printed and evaluated. In this example we are taking some addition expressions using integer variables and trying to see what’s the given expression along with the final value.

#include <iostream>
#include <unistd.h>
#include <sstream>

using namespace std;

class ExpressionVisitor;
class Expression
{
	public:
		virtual void accept (ExpressionVisitor* visitor) = 0;
};

class AdditionExpression: public Expression
{
	public:
		Expression *left;
		Expression *right;

		AdditionExpression (Expression *l, Expression *r):
			left (l), right (r) 
		{}

		~AdditionExpression ()
		{
			delete left;
			delete right;
		}
		virtual void accept (ExpressionVisitor* visitor);
};

class IntExpression: public Expression
{
	public:
		int value;
		IntExpression (int val): value (val)
		{}
		virtual void accept (ExpressionVisitor* visitor);
};

class ExpressionVisitor
{
	public:
		virtual void visit (AdditionExpression *add) = 0;
		virtual void visit (IntExpression *ie) = 0;
};

void AdditionExpression::accept (ExpressionVisitor* visitor)
{
	visitor->visit (this);
}

void IntExpression::accept (ExpressionVisitor* visitor)
{
	visitor->visit (this);
}

class ExpressionPrinter: public ExpressionVisitor
{
	public:
		ostringstream oss;
		void visit (AdditionExpression *add) override
		{
			oss << "(";
			add->left->accept (this);
			oss << " + ";
			add->right->accept (this);
			oss << ")";
		}
		void visit (IntExpression *ie) override
		{
			oss << ie->value;
		}
};

class ExpressionEvaluator: public ExpressionVisitor
{
	public:
		int exp;
		ExpressionEvaluator (): exp (0)
		{}
		void visit (AdditionExpression *add) override
		{
			add->left->accept (this);
			add->right->accept (this);
		}

		void visit (IntExpression *ie) override
		{
			exp += ie->value;
		}
};

int main ()
{
	auto e = new AdditionExpression (new IntExpression (5), new AdditionExpression (
													new IntExpression (8),
													new IntExpression (10)));

	ExpressionPrinter ep;
	ep.visit (e);
	cout << "Expression is: " << ep.oss.str () << endl;

	ExpressionEvaluator ev;
	ev.visit (e);
	cout << "Value of above Expression is : " << ev.exp << endl;
}

Output of above example is as shown:

Expression is: (5 + (8 + 10))
Value of above Expression is : 23

Advantages:

  • If the internal logic of any existing visitor operation changes, then only corresponding visitor classes needs to be modified.
  • Adding a new operation is quite easy as it doesn’t require any of the element class to be modified.

Disadvantages:

  • In case of any new element addition such as SubstractionExpression, all visitor classes needs to be modified.

Leave a Reply

Your email address will not be published. Required fields are marked *