Visitor Pattern Explanation
Asked Answered
T

2

31

So I've read up all the documentation about the Visitor pattern, and I'm still mightily confused. I've taken this example from another SO question, could someone help me understand? For instance when do we use a visitor design pattern? I think I may have understood some of it, but I'm just not able to see the bigger picture. How do I know when I can use it?

class equipmentVisited
{
  virtual void accept(equipmentVisitor* visitor) = 0;
}

class floppyDisk : public equipmentVisited
{
  virtual void accept(equipmentVisitor* visitor);
}

class processor : public equipmentVisited
{
  virtual void accept(equipmentVisitor* visitor);
}

class computer : public equipmentVisited
{
  virtual void accept(equipmentVisitor* visitor);
}

class equipmentVisitor
{
  virtual void visitFloppyDisk(floppyDisk* );
  virtual void visitProcessor(processor* );
  virtual void visitComputer(computer* );
}

// Some additional classes inheriting from equipmentVisitor would be here

equipmentVisited* visited;
equipmentVisitor* visitor;

// Here you initialise visited and visitor in any convenient way

visited->accept(visitor);
Trestle answered 12/4, 2012 at 0:58 Comment(2)
for a complete example, see the java example in en.wikipedia.org/wiki/Visitor_pattern#Java_exampleInfringe
In that example you could easily make equipmentVisitor only use the function visit, with three overloaded functions each taking a different derived equipemtVisited class as the parameter. Only I'd make that class an equipmentVisitor.Celadon
W
44

Visitor pattern is used to implement double dispatch. In plain words it means that the code that gets executed depends on runtime types of two objects.

When you call a regular virtual function, it is a single dispatch: the piece of code that gets executed depends on the runtime type of a single object, namely, the one the virtual method of which you are calling.

With the visitor pattern, the method that is being called ultimately depends on the type of two objects - the type of the object implementing the equipmentVisitor, and the type of the object on which you call accept (i.e. the equipmentVisited subclass).

There are other ways to implement double dispatch in C++. Item 31 of Scott Meyer's "More Effective C++" treats this subject in depth.

Wifehood answered 12/4, 2012 at 1:10 Comment(3)
So its like any subclass of equipmentVisited can call the function 'accept' of any of the subclasses of equipmentVistor? Is that correct?Trestle
@dasblinkenlight - I never "got" the visitor pattern! Still struggle with it. So, please respond to this question: I have a Master class that inherits from a Client class. This way the Master can do everything that the Client can do plus a few more stuff. If I want to use these classes polymorphically then I need a generic base class. PROBLEM: Now my Master class is inheriting from two classes. Do you think the Visitor Pattern could solve my problem?Bowls
@Bowls Why would you need a generic base class? Why can't you just use use Client as the base pointer/reference type? Polymorphism works with any form of inheritance, not just the restricted 'interface' pattern. Master inherits from Client, so a pointer- or reference-to-Client can be used to invoke virtual methods on a Master. Thinking you need to add another base class into the mix is mistaken and a totally redundant hit to efficiency. That's the problem.Backhander
S
21

I think the name of the pattern Visitor is quite unfortunate. Instead of the word visitor I would say Functor or Operator and instead of 'visit' I would say 'apply'.

My understanding of visitor pattern is as follows:

In template meta-programming (STL/BOOST) (compile time binding) you can achieve (the orthogonal design) the separations of operations from structures, by the means of function objects (Functors.) For example in

template <class RandomAccessIterator, class Compare>
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

the comp is a functor/operator representing 'less than' operation in a very generic way, so you don't have to have many variants of sort function:

For Visitor pattern you want to achieve something similar but in the case of run time (late) binding:

You want to simplify the interface of A, you want to keep possibility for future extensions (new operations working with A) and you want to achieve the stability of interface of A in the case of those extensions.

From the original 'fat' class:

class A
{ 
  public:
    virtual void function_or_operation_1();//this can be implemented in terms of public interface of the other functions
    virtual void function_or_operation_2();
    //..etc

    virtual void function_or_operation_N();
  public:
    //stable public interface, some functions of procedures

  private:
  //....
}

you remove as many function from public interface as possible (as long as they can be implemented in terms of the non-extracted functions of the same public interface ) and represent the operations as the functor objects or objects from a new Functor hierarchy:

You reduce the number of function in the base class A by having very generic interface using forward declared Functor_or_Operator:

 class Functor_or_Operator;
 class A
 {
   public:
     virtual void apply(Functor_or_Operator*);//some generic function operates on this objects from A hierarchy 
   //..etc
   public:
     //stable public interface, some functions

   private: 
     //....
 }

//Now you have N(=3) classes in A hierarchy (A,B,C) and M operations or functions represented by classes in Functor_or_Operator hierarchy You need to implement N*M definitions of how the every operation from Functor_or_Operator works on every class in A hierarchy. The big thing is, you can do it without changing the interface of the class 'A'. The declaration of the class 'A' becomes very stable in the case of the new additions when introducing new operations or functions working with objects of A hierarchy or in case of new derived classes in the A hierarchy. The stability of A (no changes to A) in the presence of additions is important to avoid costly (and sometimes impossible) recompilation of software which includes headers of A on many places.

For every new class in the A hierarchy, you extend the definition of base Functor_or_Operator, You add new implementation files, but you never need to touch the header of base class A (usually interface or abstract class).

  class Functor_or_Operator 
  {
    virtual void apply(A*)=0;
    virtual void apply(B*)=0;
    virtual void apply(C*)=0;
  }

  void A::apply(Functor_or_Operator* f) 
  { f->apply(this);} //you need this only if A is not abstract (it is instantiable)

  class B:public A
  {
    public:
     void apply(Functor_or_Operator* f) { f->apply(this);} //dynamic dispatch , you call polymhorphic Functor f on this object
     //..the rest of B implementation.
  }

  class C:public A
  {
    public:
     void apply(Functor_or_Operator* f) { f->apply(this);} //dynamic dispatch , you call polymorfic Functor f on this object
    //..the rest of C implementation.
  }

  class Functor_or_Operator_1:public Functor_or_Operator
  {
     public:
        //implementations of application of a function represented by Functor_or_Operator_1 on each A,B,C
        void apply(A*) {}//( only if A is instantiable,not an abstract class)
        void apply(B*) {}
        void apply(C*) {}
  }

  class Functor_or_Operator_2:public Functor_or_Operator
  {
    public:
      //implementations of application of a function represented by Functor_or_Operator_2 on each A,B,C
       void apply(A*) {}//( only if A is instantiable,not an abstract class)
       void apply(B*) {}
       void apply(C*) {}
  }
Selfdefense answered 1/2, 2014 at 4:21 Comment(1)
Bad, bad boy. You have done a big damage to this profession. Now everybody is going to understand the visitor pattern.Rosina

© 2022 - 2024 — McMap. All rights reserved.