Solved

Visitor pattern

Posted on 2010-09-13
8
761 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 450 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
 
LVL 22

Assisted Solution

by:ambience
ambience earned 50 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
What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

 
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

What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

Exception Handling is in the core of any application that is able to dignify its name. In this article, I'll guide you through the process of writing a DRY (Don't Repeat Yourself) Exception Handling mechanism, using Aspect Oriented Programming.
Container Orchestration platforms empower organizations to scale their apps at an exceptional rate. This is the reason numerous innovation-driven companies are moving apps to an appropriated datacenter wide platform that empowers them to scale at a …
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…
The viewer will learn how to use the return statement in functions in C++. The video will also teach the user how to pass data to a function and have the function return data back for further processing.

758 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

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now