Wednesday, February 9, 2011

Behavioral Patterns - Visitor Pattern

Definition

Define a new operation to deal with the classes of the elements without changing their structures


Intent

  • Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
  • The classic technique for recovering lost type information.
  • Do the right thing based on the type of two objects.
  • Double dispatch

Problem

Many distinct and unrelated operations need to be performed on node objects in a heterogeneous aggregate structure. You want to avoid “polluting” the node classes with these operations. And, you don’t want to have to query the type of each node and cast the pointer to the correct type before performing the desired operation.
.

Where to use & benefits

  • Add operations on a bunch of classes which have different interfaces.
  • Traverse the object structure to gather related operations
  • Easy to add new operations.
  • Crossing class hierarchies may break encapsulation.
  • Related patterns include
    • Composite, which may be applied in a visitor pattern.
    • Interpreter, which may be used to go through structure and define new operation in a visitor pattern. 


    Motivation

    Collections are data types widely used in object oriented programming. Often collections contain objects of different types and in those cases some operations have to be performed on all the collection elements without knowing the type.

    A possible approach to apply a specific operation on objects of different types in a collection would be the use if blocks in conjunction with 'instanceof' for each element. This approach is not a nice one, not flexible and not object oriented at all. At this point we should think to the Open Close principle and we should remember from there that we can replace if blocks with an abstract class and each concrete class will implement its own operation.

    Intent

  • Represents an operation to be performed on the elements of an object structure.
  • Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Implementation

Visitor Pattern - UML Class Diagram
The participants classes in this pattern are:
  • Visitor - This is an interface or an abstract class used to declare the visit operations for all the types of visitable classes. Usually the name of the operation is the same and the operations are differentiated by the method signature: The input object type decides which of the method is called.
  • ConcreteVisitor - For each type of visitor all the visit methods, declared in abstract visitor, must be implemented. Each Visitor will be responsible for different operations. When a new visitor is defined it has to be passed to the object structure.
  • Visitable - is an abstraction which declares the accept operation. This is the entry point which enables an object to be "visited" by the visitor object. Each object from a collection should implement this abstraction in order to be able to be visited.
  • ConcreteVisitable - Those classes implements the Visitable interface or class and defines the accept operation. The visitor object is passed to this object using the accept operation.
  • ObjectStructure - This is a class containing all the objects that can be visited. It offers a mechanism to iterate through all the elements. This structure is not necessarily a collection. In can be a complex structure, such as a composite object.

Structure

The Element hierarchy is instrumented with a “universal method adapter”. The implementation of accept() in each Element derived class is always the same. But – it cannot be moved to the Element base class and inherited by all derived classes because a reference to this in the Element class always maps to the base type Element.
Visitor scheme
When the polymorphic firstDispatch() method is called on an abstract First object, the concrete type of that object is “recovered”. When the polymorphic secondDispatch() method is called on an abstract Second object, its concrete type is “recovered”. The application functionality appropriate for this pair of types can now be exercised.
Visitor scheme

  • Example

    The following is a dummy program. Two interfaces involved: Visitor and Pizza. The Pizza system is completely independent. "How to get it" tries to add new operations to the Pizza system. It is done by adding another interface Visitor and parameterizing Pizza interface in the abstract method visit(composite pattern). The "how to get" classes implement Visitor interface and make a connection with Pizza system.
    import java.util.*;
    interface Visitor {
        public void visit(Pizza p);
    }
    interface Pizza {
        public String order();
    }
    class PopJohn implements Pizza {
        final String name = "PopJohn";
        public String order() {
            return name;
        }
    }
    class PizzaHut implements Pizza {
        final String name = "PizzaHut";
        public String order() {
            return name;
        }
    }
    class GodFather implements Pizza {
        final String name = "GodFather";
        public String order() {
            return name;
        }
    }
    class ByPickup implements Visitor {
        private String name;
        private final String method = "By pick up";
        public void visit(Pizza p) {
            name = p.order();
        }
        
        public String toString() {
             return name + " " + method;
        }
    }
    class ByEatin implements Visitor {
        private String name;
        private final String method = "By eat in";
        
        public void visit(Pizza p) {
            name = p.order();
        }
        
        public String toString() {
            return name + " " + method;
        }
    }
    class ByDelivery implements Visitor {
        private String name;
        private final String method = "By delivery";
        
        public void visit(Pizza p) {
            name = p.order();
        }
        
        public String toString() {
             return name + " " + method;
        }
    }
    class Dinner {
        public Pizza getDinner() {
            switch ((int)(Math.random()*3)){
                case 0: return new PopJohn(); 
                case 1: return new PizzaHut(); 
                case 2: return new GodFather(); 
                default: return null;
            }
        }
        public Visitor howto() {
            switch ((int)(Math.random()*3)){
                case 0: return new ByPickup(); 
                case 1: return new ByEatin(); 
                case 2: return new ByDelivery(); 
                default: return null;
            }
        }    
    }
    class Test {
        
        public static void main(String[] args) {
            List pizzaList = new ArrayList();
                pizzaList.add(new PopJohn());
                pizzaList.add(new PizzaHut());
                pizzaList.add(new GodFather());
            Iterator it = pizzaList.iterator();
            System.out.println("How many pizza restaurants in this area?");
            while (it.hasNext()) {
               System.out.println(((Pizza)it.next()).order());
            }
            Dinner d = new Dinner();
            Pizza pza = d.getDinner();
            Visitor v = d.howto();
            v.visit(pza);
            System.out.println("\nWhich store for dinner?");
            System.out.println(v);
        }
    }
    //run it several times.
    java Test
    How many pizza restaurants in this area?
    PopJohn
    PizzaHut
    GodFather
    
    Which restaurant for dinner?
    GodFather By delivery
    
    java Test
    How many pizza restaurants in this area?
    PopJohn
    PizzaHut
    GodFather
    
    Which restaurant for dinner?
    PizzaHut By pick up
    
    java Test
    How many pizza restaurants in this area?
    PopJohn
    PizzaHut
    GodFather
    
    Which restaurant for dinner?
    PizzaHut By delivery






    Read full article
    Problem.“If you want to add a new Visitable object, you have to change the Visitor interface, and then implement that method in each of your Visitors.”
    Solution. With the ReflectiveVisitor, you only need one method in the Visitor interface - visit(Object). All other visit() methods can be added later as point-to-point coupling is required.
    import java.lang.reflect.Method;
    
    // The "element" hierarchy
    
    interface Element {
       public void accept( ReflectiveVisitor v );
    }
    class This implements Element {
       public void   accept( ReflectiveVisitor v ) { v.visit( this ); }
       public String thiss()                       { return "This"; }
    }
    class That implements Element {
       public void   accept( ReflectiveVisitor v ) { v.visit( this ); }
       public String that()                        { return "That"; }
    }
    class TheOther implements Element {
       public void   accept( ReflectiveVisitor v ) { v.visit( this ); }
       public String theOther()                    { return "TheOther"; }
    }
    
    // The "operation" hierarchy
    
    abstract class ReflectiveVisitor {
      abstract public void visit( Object o );
    
      public void visitTheOther( TheOther e ) {
        System.out.println( "ReflectiveVisitor: do Base on " + e.theOther() );
      }
    
      // 1. Look for visitElementClassName() in the current class
      // 2. Look for visitElementClassName() in superclasses
      // 3. Look for visitElementClassName() in interfaces
      // 4. Look for visitObject() in current class
      protected Method getMethod( Class c ) {
        Class  newc = c;
        Method m    = null;
        while (m == null  &&  newc != Object.class) {
          String method = newc.getName();
          method = "visit" + method.substring( method.lastIndexOf('.') + 1 );
          try {
            m = getClass().getMethod( method, new Class[] { newc } );
          } catch (NoSuchMethodException ex) {
            newc = newc.getSuperclass();
          }
        }
        if (newc == Object.class) {
          // System.out.println( "Searching for interfaces" );
          Class[] interfaces = c.getInterfaces();
          for (int i=0; i < interfaces.length; i++) {
            String method = interfaces[i].getName();
            method = "visit" + method.substring( method.lastIndexOf('.') + 1 );
            try {
              m = getClass().getMethod( method, new Class[] { interfaces[i] } );
            } catch (NoSuchMethodException ex) { }
          }
        }
        if (m == null)
          try {
            m = getClass().getMethod( "visitObject", new Class[] { Object.class } );
          } catch (Exception ex) { }
        return m;
      }
    }
    
    class UpVisitor extends ReflectiveVisitor {
      public void visit( Object o ) {
        try {
          getMethod( o.getClass() ).invoke( this, new Object[] { o } );
        } catch (Exception ex) {
          System.out.println( "UpVisitor - no appropriate visit() method" );
        }
      }
      public void visitThis( This e ) {
        System.out.println( "UpVisitor: do Up on " + e.thiss() );
      }
      public void visitObject( Object e ) {
        System.out.println( "UpVisitor: generic visitObject() method" );
      }
    }
    
    class DownVisitor extends ReflectiveVisitor {
      public void visit( Object o ) {
        try {
          getMethod( o.getClass() ).invoke( this, new Object[] { o } );
        } catch (Exception ex) {
          System.out.println( "DownVisitor - no appropriate visit() method" );
      } }
      public void visitThat( That e ) {
        System.out.println( "DownVisitor: do Down on " + e.that() );
      }
    }
    
    class VisitorDemo {
      public static void main( String[] args ) {
        Element[]    list = { new This(), new That(), new TheOther() };
        UpVisitor    up   = new UpVisitor();
        DownVisitor  down = new DownVisitor();
        for (int i=0; i < list.length; i++)
          list[i].accept( up );
        for (int i=0; i < list.length; i++)
          list[i].accept( down );
      }
    }
    UpVisitor: do Up on This UpVisitor: generic visitObject() method ReflectiveVisitor: do Base on TheOther DownVisitor - no appropriate visit() method DownVisitor: do Down on That ReflectiveVisitor: do Base on TheOther

No comments:

Post a Comment