Visitor Pattern

1. Usage

Adds new operations to existing object structure without modifying that structure. Separates an algorithm from an object structure on which it operates.

Example:
An example would be a guest visiting a household. A guest talks to each family member, but he talks to each one in a different way – he will not talk the same with adults as he will with children. The guest has indispensable knowledge of how to talk to certain household member. This knowledge however does not have an impact on the household.

2. UML class diagram

Visitor Design Pattern

The Visitor can visit objects that don’t have a common parent class.

Although the ConcreteElement nodes may represent unrelated classes, they all should derive from a common Element base.

The client instantiates a ConcreteVisitor.

The traversal algorithm invokes Element::accept(Visitor) for each node.

The ConcreteElement calls Visitor::visit(ConcreteElement).

The ConcreteVisitor now uses the ConcreteElement interface to carry out node-specific tasks.

3. Pros

  • cleaner code – factors out type specific event handling from classes and centralises it in a Visitor
  • easy to add a new “operation” for all Visitable classes – an operation is implemented as a Visitor subclass with a handler method for each Visitable object type
  • can traverse multiple object types in the same traversal – unlike Iterator which can only traverse one type at a time
  • useful for running a variety of reports – without Visitor every class that you’d want to report on would have to have a custom method per report

4. Cons

  • if a new Visitable class is added, all Visitor subclasses have to be extended to support it
  • might break encapsulation – the Visitor needs access to the elements details

5. Source code

// From http://www.patterns.pl/visitor.html
    #include <iostream>
    #include <vector>
    using namespace std;
    class ConcreteElementA;
    class ConcreteElementB;
    class Visitor
    {
    public:
      virtual void visitConcreteElementA(ConcreteElementA& visitable) = 0;
      virtual void visitConcreteElementB(ConcreteElementB& visitable) = 0;
    };
    class ConcreteVisitor : public Visitor
    {
    public:
      void visitConcreteElementA(ConcreteElementA& visitable) override
      {
        cout << "ConcreteVisitor perform opetation on ConcreteElementA" << endl;
      }
      void visitConcreteElementB(ConcreteElementB& visitable) override
      {
        cout << "ConcreteVisitor perform operation on ConcreteElementB" << endl;
      }
    	
    };
    class Visitable
    {
    public:
      virtual void accept(Visitor& visitor) = 0;
    };
    class ConcreteElementA : public Visitable
    {
    public:
      void accept(Visitor &visitor) override { visitor.visitConcreteElementA(*this); }
    };
    class ConcreteElementB : public Visitable
    {
    public:
      void accept(Visitor &visitor) override { visitor.visitConcreteElementB(*this); }
    };
    // Composite in practice
    class ObjectStructure : public Visitable
    {
      vector<Visitable*> elements;
    public:
      ObjectStructure() {}
      void addElement(Visitable *element) { elements.push_back(element); }
      void accept(Visitor &visitor) override
      {
        for (auto &e : elements)
        {
          e->accept(visitor);
        }
      }
    };
    int main()
    {
      ConcreteVisitor visitor;
      ConcreteElementA elementA;
      ConcreteElementB elementB;
      ObjectStructure objectStructure;
      objectStructure.addElement(&elementA);
      objectStructure.addElement(&elementB);
      objectStructure.accept(visitor);
      return 0;
    }

Leave a Reply

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