[Webinar] Streamline your web hosting managementRegister Today

x
?
Solved

Visitor pattern

Posted on 2010-09-13
8
Medium Priority
?
785 Views
Last Modified: 2013-11-11
Hi

I have a question about the visitor pattern. Here is an example from evilrix:

 
#include <iostream>

struct CItem1;
struct CItem2;
struct CItem3;

struct Visitor
{
        void visit(CItem1 * p);
        void visit(CItem2 * p);
        void visit(CItem3 * p);
};

struct CItem
{
        virtual void accept(Visitor * p) = 0;
};

struct CItem1 : CItem
{
        void accept(Visitor * p)
        {
                p->visit(this);
        }

        void hello1()
        {
                std::cout << "hello from CItem1" << std::endl;
        }
};

struct CItem2 : CItem
{
        void accept(Visitor * p)
        {
                p->visit(this);
        }

        void hello2()
        {
                std::cout << "hello from CItem2" << std::endl;
        }
};

struct CItem3 : CItem
{
        void accept(Visitor * p)
        {
                p->visit(this);
        }

        void hello3()
        {
                std::cout << "hello from CItem3" << std::endl;
        }
};

void Visitor::visit(CItem1 * p)
{
        p->hello1();
}

void Visitor::visit(CItem2 * p)
{
        p->hello2();
}

void Visitor::visit(CItem3 * p)
{
        p->hello3();
}

void foo(CItem * p)
{
        Visitor v;
        p->accept(&v);
}

int main()
{
        CItem1 i1;
        CItem3 i2;
        CItem3 i3;

        foo(&i1);
        foo(&i2);
        foo(&i3);
};

Open in new window


Simple and nice.
Probably, I do not understand the pattern well. Now without the pattern implemented I have 7 methods like foo(CItem*). In each method I call item->GetType() and then have a switch statement.

So I will have 1 visitor and 7 accept and 7 visit functions?
0
Comment
Question by:pgnatyuk
  • 3
  • 3
  • 2
8 Comments
 
LVL 40

Expert Comment

by:evilrix
ID: 33669235
I will look at this in a little bit.
0
 
LVL 40

Accepted Solution

by:
evilrix earned 1800 total points
ID: 33669494
>> So I will have 1 visitor and 7 accept and 7 visit functions?
the idea of the visitor patterns is that it decouples the functionality from the object. There are many reasons this can be useful, one of them being if you have a dynamic base class type but you need access to the static subclass. Once way would me to implement virtual functions but if you have an existing class hierarchy this may not be feasible. Instead with some minimal changes to your class design you can support the visitor pattern.

This works by passing a visitor object into your dynamic type via a polymorphic accept function and then calling the visit member on the visitor object passing in a this pointer. Because you implement the accept function in the subclass you are passing in a this pointer in the context of the subclass and not the dynamic base class so the visitor is passed a pointer to the static type. Thus, you have resolved from a dynamic to a static type.

You should have one visitor per unit of logic. Each visitor only implements an overloaded visit function that performs one specific action with the various subclass types you want to support. Think of each visitor as a new interface function for each class.

Below is an example of two functions. See how each visitor derives from the Visit base class? This differs from the original example because in that we only had one visitor.


#include <iostream>

struct CItem1;
struct CItem2;
struct CItem3;

struct Visitor
{
        virtual void visit(CItem1 * p) = 0;
        virtual void visit(CItem2 * p) = 0;
        virtual void visit(CItem3 * p) = 0;
        virtual ~Visitor(){}
};

struct Visitor_Hello : Visitor
{
        void visit(CItem1 * p);
        void visit(CItem2 * p);
        void visit(CItem3 * p);
};

struct Visitor_Goodbye : Visitor
{
        void visit(CItem1 * p);
        void visit(CItem2 * p);
        void visit(CItem3 * p);
};

struct CItem
{
        virtual void accept(Visitor * p) = 0;
};

struct CItem1 : CItem
{
        void accept(Visitor * p)
        {
                p->visit(this);
        }

        void hello1()
        {
                std::cout << "hello from CItem1" << std::endl;
        }

        void goodbye1()
        {
                std::cout << "goodbye from CItem1" << std::endl;
        }
};

struct CItem2 : CItem
{
        void accept(Visitor * p)
        {
                p->visit(this);
        }

        void hello2()
        {
                std::cout << "hello from CItem2" << std::endl;
        }

        void goodbye2()
        {
                std::cout << "goodbye from CItem2" << std::endl;
        }
};

struct CItem3 : CItem
{
        void accept(Visitor * p)
        {
                p->visit(this);
        }

        void hello3()
        {
                std::cout << "hello from CItem3" << std::endl;
        }

        void goodbye3()
        {
                std::cout << "goodbye from CItem3" << std::endl;
        }
};

void Visitor_Hello::visit(CItem1 * p)
{
        p->hello1();
}

void Visitor_Hello::visit(CItem2 * p)
{
        p->hello2();
}

void Visitor_Hello::visit(CItem3 * p)
{
        p->hello3();
}

void Visitor_Goodbye::visit(CItem1 * p)
{
        p->goodbye1();
}

void Visitor_Goodbye::visit(CItem2 * p)
{
        p->goodbye2();
}

void Visitor_Goodbye::visit(CItem3 * p)
{
        p->goodbye3();
}

void hello(CItem * p)
{
        Visitor_Hello v;
        p->accept(&v);
}

void goodbye(CItem * p)
{
        Visitor_Goodbye v;
        p->accept(&v);
}


int main()
{
        CItem1 i1;
        CItem3 i2;
        CItem3 i3;

        hello(&i1);
        hello(&i2);
        hello(&i3);

        goodbye(&i1);
        goodbye(&i2);
        goodbye(&i3);
};

Open in new window

0
 
LVL 33

Author Comment

by:pgnatyuk
ID: 33669597
Thanks.
I didn't get the part about " you have resolved from a dynamic to a static type". Why you use these terms "static" and "dynamic"? How it's related to the visitor pattern?
From your answer I already can implement the pattern in my program.
 
0
The new generation of project management tools

With monday.com’s project management tool, you can see what everyone on your team is working in a single glance. Its intuitive dashboards are customizable, so you can create systems that work for you.

 
LVL 22

Assisted Solution

by:ambience
ambience earned 200 total points
ID: 33669609
Yes you will need to have seven of those. Another way to look at it is that a each of the accept() and visit() form the basic visitor pattern, so rather than counting them as two we count them as a single change. Visitor forms a matrix, ie. the number of types visited as rows and number of visitors as columns (or vice versa). So in this case there are three types and one visitor hence the matrix is like
-------  Visitor
Item1
Item2
Item3
Then we need 1 x 3 implementations of the visitor patterns (a pair of accept/visit). If you add another visitor class say Visitor2, then you need 2 x 3 i.e. 6 implementations. If you add one more type like Item4, then another row must be added to the matrix and that means 1 more set of accept/visit pair. I hope you get the idea. Visitor suffers from this patricular drawback that all types are coupled and you will have to make a change to each of the types involved to add a new visitor, or the visitors to add a new type.
In an variation of visitor called "Acyclic Visitor," you can reduce the "visitor class" to an interface and have multiple implementations of a concrete visitor. A little use of polymorphic type checking would then be needed, but the types will be decoupled from visitor and adding new types to the system is much easier, i.e. fewer methods are needed. See the code sample
 

#include <iostream>

struct CItem1;
struct CItem2;
struct CItem3;

/* Just a marker interface */
struct Visitor {
	virtual ~Visitor() {}
};

struct Item1Visitor : Visitor {
	virtual void visit(CItem1 * p) {
        p->hello1();
	}
};

struct Item2Visitor : Visitor {
	virtual void visit(CItem2 * p) {
        p->hello2();
	}
};

struct CItem
{
	virtual void accept(Visitor * p) = 0;
};

struct CItem1 : CItem
{
	void accept(Visitor * p) {
		Item1Visitor* visitor = dynamic_cast<Item1Visitor*>(p);
		if(visitor) p->visit(visitor);
	}

	void hello1() {
		std::cout << "hello from CItem1" << std::endl;
	}
};

struct CItem2 : CItem
{
	void accept(Visitor * p) {
		Item2Visitor* visitor = dynamic_cast<Item2Visitor*>(p);
		if(visitor) p->visit(visitor);
	}

	void hello1() {
		std::cout << "hello from CItem1" << std::endl;
	}
};

/* A visitor that can visit all items. This does not have to be known to all types, they only operator on interfaces */
struct AllVisitor : Item1Visitor, Item2Visitor {
};

/* Visits item2 twice. */
struct MyCustomVisitor : Item1Visitor, Item2Visitor {
	virtual void visit(CItem2 * p) {
        Item2Visitor::visit(p);
        Item2Visitor::visit(p);
	}
};

void foo(CItem * p)
{
	AllVisitor v;
	p->accept(&v);
}

int main()
{
	CItem1 i1;
	CItem3 i2;
	CItem3 i3;

	foo(&i1);
	foo(&i2);
	foo(&i3);
};

Open in new window

0
 
LVL 22

Expert Comment

by:ambience
ID: 33669629
In acyclic visitor you would need one set of accept/visit for every new class, plus for aggregate visitors like AllVisitor, those must be changed too. In my sample, I added default implementations to Item1Visitor etc., but you can keept those are empty abstract methods if you wish like
struct Item2Visitor : Visitor {
virtual void visit(CItem2 * p) =0;
};
The biggest advantage is the decoupling achieved.
0
 
LVL 40

Expert Comment

by:evilrix
ID: 33669672
>> I didn't get the part about " you have resolved from a dynamic to a static type".
>> Why you use these terms "static" and "dynamic"? How it's related to the visitor pattern?

See code below to define difference between dynamic and static type and how this relates to the visitor pattern.





struct CItem { virtual ~CItem(){} };
struct CItemEx : CItem{}

foo(CItem* dynamic_type)
{
   // The static type might be CItem  or CItemEx but we don't know without either
   // a discriminator or dynamic_cast but visitor pattern can resolve this for us

   // Let the visitor pattern handle it, dynamic casts are slow and discriminators
   // are ugly and not very OO like, the C++ type system can resolve this for us.
   hello(dynamic_type);
   goodbye(dynamic_type);

}

CItemEx itemEx
foo(&itemEx);

Open in new window

0
 
LVL 33

Author Comment

by:pgnatyuk
ID: 33675741
Thanks, evilrix. I think, that's what I need.

ambience, I think, here is a mistake:

void accept(Visitor * p) {
      Item2Visitor* visitor = dynamic_cast(p);
      if(visitor) p->visit(visitor);
}

Anyway, thank you.

int main()
{
	CItem* array[3] = { 0 };
	array[0] = new CItem1;
	array[1] = new CItem2;
	array[2] = new CItem3;
	
	for (int i = 0; i < 3; ++i)
	{
		hello(array[i]);
		goodbye(array[i]);
	}
}

Open in new window

0
 
LVL 33

Author Closing Comment

by:pgnatyuk
ID: 33675769
Thank you.
0

Featured Post

Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Go is an acronym of golang, is a programming language developed Google in 2007. Go is a new language that is mostly in the C family, with significant input from Pascal/Modula/Oberon family. Hence Go arisen as low-level language with fast compilation…
"Disruption" is the most feared word for C-level executives these days. They agonize over their industry being disturbed by another player - most likely by startups.
The goal of the tutorial is to teach the user how to use functions in C++. The video will cover how to define functions, how to call functions and how to create functions prototypes. Microsoft Visual C++ 2010 Express will be used as a text editor an…
The goal of the video will be to teach the user the concept of local variables and scope. An example of a locally defined variable will be given as well as an explanation of what scope is in C++. The local variable and concept of scope will be relat…
Suggested Courses

612 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question