some thoughts of composite & visitor pattern

5257 ワード

At first, let's look the typical class structure diagram of composite pattern:
 
Composite pattern is very suitable for Tree structure. The picture showed above defines a Interface Node and two sub concrete tree types  : Leaf  and Branch.  Branch may contains sub branch and leaf. The sample codes for the example are listed below:
public Interface Node{
       public void print();
}

public class leaf implements Node{
      public void print(){ 
             System.out.println("visit leaf...");
      }
}

public class branch implements Node{
       private List<Node> subNodes;

       public void addSubNode(Node subNode){}

       public void removeSubNode(Node subNode){}

       public void print(){
          for(Node node : subNodes){
               node.print();
          }
       }
}

 The code is so unpractical that the print method just show its node type. In a general way,  the requirement might  extended in two directions respectively: 
  • add new method for each node
  • add new node type in tree hierarchy.

  • under the first situation,  if we need print the number of  sub nodes  for each node, the easiest way, also the only way, is to  change the print method and plus new logic. That is to say, we need to modify the existed system to satisfy new requirement, which violate the open-closed principle of OOD. So, how can we do it with a  better way? May be a available method is to seperate the hebavior from the node class, namely introduce new class to response for behavior, and node class only contains state data.  So now, it is time that visitor pattern show its power.
    the class diagram of visitor pattern as follows:
    Compare to the diagram of composite pattern.  it has several extra classes : an integerface Visitor and class PrintVisitor. these classes provide the operation to visit nodes. The sample codes listed below:
    public Interface Node{
           public void print();
           public void accept(Visitor v);
    }
    
    public class leaf implements Node{
          public void print(){ 
                 System.out.println("visit leaf...");
          }
          public void accept(Visitor v){
                 v.visit(this); 
          }
    }
    
    public class branch implements Node{
           private List<Node> subNodes;
    
           public void addSubNode(Node subNode){}
    
           public void removeSubNode(Node subNode){}
    
           public void print(){
              for(Node node : subNodes){
                   node.print();
              }
           }
           public void accept(Visitor v){
                 v.visit(this); 
          }
    }
    
    public interface Visitor{
          public void visit(Leaf leaf);
          public void visit(Branch branch);
    }
    
    public class PrintVisitor implements Visitor{
         public void visit(Leaf leaf){
                leaf.print();
         }
         public void visit(Branch branch){
                branch.print();
         }
    } 

     Now, we can simply add a new 'visitor' class to extent node behavior, and need not any modification of existed system.  Visitor pattern works wonderfully here, but it also has some limitation.
  • it only suitable for extending node behavior, not  for adding new node type in tree hierarchy. This is the fatal defect of visitor pattern. Just as we see from the simple codes,  a Visitor encapsulate the behavior of all nodes. So if one more node type is added, the vistor interface and all concrete classes need be modified to add corresponding behavior. Luckily, A variant of visitor pattern called 'acyclic visitor' pattern(avp for short) may solve the problem. The main idea of avp is make the Vistor Interface to be a Marker Interface, and create an corresponding visitor interface for each node type . So the codes using avp may looks like that:
    public Interface Node{
           public void print();
           public void accept(Visitor v);
    }
    
    public class leaf implements Node{
          public void print(){ 
                 System.out.println("visit leaf...");
          }
          public void accept(Visitor v){
                 LeafVisitor lv = (LeafVisitor)v;
                 lv.visit(this); 
          }
    }
    
    public class branch implements Node{
           private List<Node> subNodes;
    
           public void addSubNode(Node subNode){}
    
           public void removeSubNode(Node subNode){}
    
           public void print(){
              for(Node node : subNodes){
                   node.print();
              }
           }
           public void accept(Visitor v){
                 BranchVisitor lv = (BranchVisitor)v;
                 bv.visit(this); 
          }
    }
    
    public interface Visitor{}
    
    public interface LeafVisitor{
         public void visit(Leaf leaf);
    }
    
    public interface BranchVisitor{
        public void visit(Branch branch);
    }
    
    public class PrintVisitor implements Visitor,LeafVisitor,BranchVisitor{
         public void visit(Leaf leaf){
                leaf.print();
         }
         public void visit(Branch branch){
                branch.print();
         }
    } 
    
     In the 'accpet' method of class node class,  firstly, using up-casting, changes the argument to the visitor interface correspond with the node type. if success, then invokes the 'visit' method of the visitor.