Thread: Visitor / Decorator Pattern

  1. #1
    Registered User
    Join Date
    May 2008
    Location
    Paris
    Posts
    248

    Visitor / Decorator Pattern

    Hello everybody,

    Two patterns seem to be able to add functionality to a class, without modifying the class itself : the Decorator pattern and the Visitor pattern. I haven't really understood the difference between them two.

    About the Visitor pattern : I hardly see the use of it when its visitants are well designed and it seems old-fashioned to me. Here's my test implementation :
    Code:
    class AbstractElement
    {
     public:
      virtual void accept(AbstractVisitor v);
    };
    
    class AbstractVisitor
    {
     public:
      virtual void visit(AbstractElement e);
    };
    
    class initVisitor : public AbstractVisitor
    {
     public:
      void visit(AbstractElement e);
    };
    
    class productVisitor : public AbstractVisitor
    {
     public:
      void visit(AbstractElement e);
    };
    now this implementation will not work :
    Code:
    void AbstractElement::accept(AbstractVisitor v)
    {
      v.visit(*this);
    }
    because "v" is an instantiation and not a pointer, so the dispatch by polymorphism fails. The functions "visit" and "accept" should hence take (smart) pointers (and thus the virtual functions can be purely virtual).

    Now they say that separation between data and algorithms on that data can be established by the Visitor pattern, by simply adding a new visitor with the algorithm in the function "visit" :
    Code:
    void productVisitor::visit(AbstractElement e)
    {
        // .... the algorithm... but on what ?!
    }
    Now's my question : on what do we apply the algorithm? On data in the visited class ? But then we'll need to specify that data already in its interface class. So the class of visited objects should not have a polymorph base/interface? I mean, the class "AbstractElement" has no reason to be?

    Thank you for your answer !!


    Mark


    edit: Since we'll need pointers to "AbstractVisitor" as argument in the function "accept", we'll need to instantiate the visitor classes somewhere. Then, if I've understood well, "accept" is a kind of call-back to the derived-class "visit" (by dispatch?).

    Are the visitor objects not just a kind of function objects, passed as arguments in some function "do_something" ? This kind of call-back seems more modern to me...
    Last edited by MarkZWEERS; 05-13-2009 at 07:41 AM.

  2. #2
    The larch
    Join Date
    May 2006
    Posts
    3,573
    because "v" is an instantiation and not a pointer, so the dispatch by polymorphism fails. The functions "visit" and "accept" should hence take (smart) pointers (and thus the virtual functions can be purely virtual).
    You could use references too. Polymorphism doesn't require that the objects are allocated on the heap, but they need to be referenced through a reference or a pointer.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  3. #3
    Registered User
    Join Date
    Apr 2008
    Posts
    890
    The Visitor pattern is a way to implement double dispatch in languages with no direct support for it. Here's a toy example program:

    Code:
    #include <iostream>
    
    class imp;
    class hellknight;
    
    // Visitor interface - must define visit functions for each type of visitable object
    
    class weapon_visitor {
    public:
    	virtual void visit(imp *) = 0;
    	virtual void visit(hellknight *) = 0;
    };
    
    // Concrete visitors
    
    class rocket_visitor : public weapon_visitor {
    public:
    	virtual void visit(imp *) 
    	{
    		std::cout << "Rocket vaporizes imp" << std::endl;
    	}
    	virtual void visit(hellknight *) 
    	{
    		std::cout << "Rocket kills hellknight" << std::endl;
    	}
    };
    
    class bullet_visitor : public weapon_visitor {
    public:
    	virtual void visit(imp *) 
    	{
    		std::cout << "Bullet kills imp" << std::endl;
    	}
    	virtual void visit(hellknight *) 
    	{
    		std::cout << "Bullet wounds hellknight" << std::endl;
    	}
    };
    
    // Visitable interface.  Declares the member function to accept visitors.
    
    class visitable_object {
    public:
    	virtual void accept(weapon_visitor *) = 0;
    };
    
    // Concrete visitable classes - implement the accept method
    
    class imp : public visitable_object {
    public:
    	virtual void accept(weapon_visitor * v) 
    	{
    		v->visit(this);
    	}
    };
    
    class hellknight : public visitable_object {
    public:
    	virtual void accept(weapon_visitor * v) 
    	{
    		v->visit(this);
    	}
    };
    
    // Client code.   Polymorphism in two directions (weapons and victims).
    
    void shoot_enemies(weapon_visitor * v) 
    {
    	visitable_object * i = new imp;
    	visitable_object * h = new hellknight;
    
    	i->accept(v);
    	h->accept(v);
    }
    
    int main() 
    {
    	shoot_enemies(new rocket_visitor);
    	shoot_enemies(new bullet_visitor);
    
    	return 0;
    }

  4. #4
    Registered User
    Join Date
    Apr 2008
    Posts
    890
    The Decorator pattern is used to add functionality to objects at runtime. Another toy (broken into headers and one .cpp in this case):

    Code:
    #ifndef INCLUDED_DECORATOR
    #define INCLUDED_DECORATOR
    
    #include "monster.h"
    
    // Decorator base class.  Must implement the interface (monster) 
    // that will be decorated.  Holds a pointer to the component and 
    // implements the interface using the contained component's 
    // implementation.  This allows us to nest decorators.
    
    class decorator: public monster {
    	monster * m;
    public:
    	virtual ~decorator() 
    	{
    		delete m;
    	}
    	decorator(monster * mon): m(mon) {}
    	virtual void attack() 
    	{
    		m->attack();
    	}
    };
    
    #endif
    
    #ifndef INCLUDED_ARMED
    #define INCLUDED_ARMED
    
    #include "decorator.h"
    
    class armed: public decorator {
    public:
    	armed(monster * m): decorator(m) {}
    	virtual void attack() {
    		decorator::attack();
    		std::cout << "firing" << std::endl;
    	}
    };
    
    #endif
    
    #ifndef INCLUDED_CHARGING
    #define INCLUDED_CHARGING
    
    #include "decorator.h"
    
    class charging: public decorator {
    public:
    	charging(monster * m): decorator(m){}
    	virtual void attack() 
    	{
    		decorator::attack();
    		std::cout << "charging" << std::endl;
    	}
    };
    
    #endif
    
    #ifndef INCLUDED_ROARING
    #define INCLUDED_ROARING
    
    #include "decorator.h"
    
    class roaring: public decorator {
    public:
    	roaring(monster *m): decorator(m) {}
    	virtual void attack() {
    		decorator::attack();
    		std::cout << "roaring" << std::endl;
    	}
    };
    
    #endif
    
    #ifndef INCLUDED_MONSTER
    #define INCLUDED_MONSTER
    
    #include <iostream>
    
    // Abstract base class that will be decorated
    
    class monster {
    public:
    	virtual ~monster() {}
    	virtual void attack() = 0;
    };
    
    #endif
    
    #ifndef INCLUDED_OGRE
    #define INCLUDED_OGRE
    
    #include "monster.h"
    
    class ogre: public monster {
    public:
    	virtual void attack() 
    	{
    		std::cout << "ogre attacking" << std::endl;
    	}
    };
    
    #endif
    
    // MAIN.CPP
    
    #include "ogre.h"
    #include "armed.h"
    #include "charging.h"
    #include "roaring.h"
    
    void attack(monster * m) 
    {
    	m->attack();
    }
    
    // Client code that builds decorated and undecorated monsters
    
    int main() {
    	// An undecorated monster
    	
    	ogre * o = new ogre;
    	attack(o);
    
    	std::cout << std::endl;
    
    	// A monster is decorated at construction time.  Note that since
    	// decorator "is a" monster, we can nest decorations.  Here we add
    	// weapon firing, roaring, and charging capability to the monster
    	// at runtime.
    
    	monster * m = new charging(new roaring(new armed(new ogre)));
    	attack(m);
    
    	std::cout << std::endl;
    
    	// Here we create an armed ogre that can't roar or charge.
    
    	monster * l = new armed(new ogre);
    	attack(l);
    
    	return 0;
    }

  5. #5
    Registered User
    Join Date
    May 2008
    Location
    Paris
    Posts
    248
    Thanks, I was studying on it.

    Isn't the key point in the Visitor pattern that it is the Visitor who initiates the action on the visited object by the double dispatch?

    I mean, isn't the function "visit" to be the first function to be called with a to-be-visited object as argument, in a function let's say main() :

    visit (on interface) -> accept (on interface) -> accept (polymorphism) -> visit (polymorphism)

    This is the only way I can see the sort of call-back to a dynamic visitor object (read a file, save a file, copy a file, ...)


    The Decorator pattern is clear to me, but thanks a lot for your efforts medievalelks !


    EDIT : do you agree that when the action to be performed on the to-be-visited object is known at compile time, the Visitor pattern is not the right one to use? I would then use a function object, of which its type is passed by template parameter.
    Last edited by MarkZWEERS; 05-14-2009 at 08:28 AM.

  6. #6
    Registered User
    Join Date
    Apr 2008
    Posts
    890
    Quote Originally Posted by MarkZWEERS View Post
    Thanks, I was studying on it.

    Isn't the key point in the Visitor pattern that it is the Visitor who initiates the action on the visited object by the double dispatch?
    The key point is that calling object and passed object are resolved dynamically, hence the double dispatch. I suppose you could invert the standard way of Visitable accepting the Visitor, but that's the way I've always seen it.

    EDIT : do you agree that when the action to be performed on the to-be-visited object is known at compile time, the Visitor pattern is not the right one to use?
    Can you give an example?

  7. #7
    Registered User
    Join Date
    May 2008
    Location
    Paris
    Posts
    248
    but that's the way I've always seen it.
    you mean the Visitor initiating the action to visit an element...
    Can you give an example?
    Code:
    void myElement :: foo(bool i)
    {
      if (i) {
        initVisitor initializer;
        initializer.Visit(this);
      }
      else {
        updateVisitor updater;
        updater.Visit(this);
      }
    }
    This is a simplified version of some code I've seen at work, and it to me seems strange that the Visitor pattern is used here, since at the time of programming we know what to do in either situation. The Visitor pattern seems to me a solution to the problem that at compile-time we do not know what "Element" object we have and we do not know what to do with it, since it depends on runtime.

  8. #8
    Registered User
    Join Date
    Apr 2008
    Posts
    890
    Quote Originally Posted by MarkZWEERS View Post
    you mean the Visitor initiating the action to visit an element...
    The initiation occurs at a higher level. Again, I refer to the canonical collision-detection example. You have a collection of game objects (monsters, walls, barrels, the player), and another collection of projectiles (bullets, rockets, fireballs, plasma bursts). All of these collision possibilities have to be accounted for, and somewhere in your code the collision will be handled with two polymorphic calls through base pointers or references. I've always seen visited->accept(visitor) call visitor->visit(this), but you could invert that relationship, I guess. I've never given much thought to it.

    Code:
    void myElement :: foo(bool i)
    {
      if (i) {
        initVisitor initializer;
        initializer.Visit(this);
      }
      else {
        updateVisitor updater;
        updater.Visit(this);
      }
    }
    This is a simplified version of some code I've seen at work, and it to me seems strange that the Visitor pattern is used here, since at the time of programming we know what to do in either situation. The Visitor pattern seems to me a solution to the problem that at compile-time we do not know what "Element" object we have and we do not know what to do with it, since it depends on runtime.
    Yes, despite the names, that's not really an implementation of the Visitor pattern. What does Visit do to myElement in both cases? Seems like you might just need two member functions on myElement for init and update...hard to tell without context.

  9. #9
    Registered User
    Join Date
    May 2008
    Location
    Paris
    Posts
    248
    The initiation occurs at a higher level. (...) I've always seen visited->accept(visitor) call visitor->visit(this)
    Then at that higher lever it is decided which Visitor is being accepted by which Element.
    but you could invert that relationship, I guess.
    It seems more logical to me. The actor decides on which object the action applies, then the actee accepts.
    This seems to be the best match with the idea of a "polymorph" call-back (with double dispatch) in the Visitor pattern wikipedia :
    It is not possible to create visitors for objects without adding a small callback method inside each class
    But I didn't find the notion of Visitor's call-back in The Gang of Four nor in the book of Alexandrescu. But it sounds appealing to me.

    Probably it is an academical discussion since both directions seem to work, but hey, no fun without ;-)

    Seems like you might just need two member functions
    Thanks, this was also my conclusion. Probably bad design...
    Last edited by MarkZWEERS; 05-15-2009 at 07:47 AM.

  10. #10
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    The Decorator pattern is applied when you need to extend the functionality of a class, but don't want (or can) derive from it. It is also useful for adapting an object to a required interface without actually changing it.

    The Visitor pattern is applied when you need to extend an entire class hierarchy with new functionality, but you don't want (or can) add a new virtual function to the base.

    Now why would you not want to do that? Well, for starters, the class hierarchy might not be under your control, so you could be genuinely unable to do so.
    Or the class hierarchy is in a different module, and to add the new operation to it would break layering. We have a lot of instances of this in the Clang compiler. Our AST is several big class hierarchies (with things like BinaryOperatorExpression, UnaryOperatorExpression, DeclarationReferenceExpression, IntegerLiteralExpression, ...), and it's in the module AST. Then we want to add a new operation, called InstantiateTemplate. In this operation, we need to walk the entire AST and create a clone which has the template parameters substituted by the actual arguments.
    Classical OOP design says that the base class Expression should get a pure virtual function InstantiateTemplate, which is implemented by all the concrete classes. However, InstantiateTemplate is a functionality of the SemanticAnalysis component (Sema), not AST. AST doesn't know Sema even exists, and we'd like to keep it that way.
    So we implement this by using the Visitor pattern. Sema has a class called TemplateExprInstantiator, which implements ExpressionVisitor. This class does the actual instantiating.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

Popular pages Recent additions subscribe to a feed