some thoughts of composite & visitor pattern

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.

  1. 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.
阅读更多

some thoughts about immutable programming

05-07

Today, when I was reading C++ View, I saw an interesting example. rnDescription:rnwe have a list in memory that holds all the friends we have. We'll also need to add it to database.rnSuppose we have a method rnvoid addFriend(Person p)rnrn friends.add(p);rn addToDb(p);//if exception's thrown out, we need to undo in friends!rnrnThe problem exists when either friends.add() or addToDb() throws exception. then we have to undo the changes we've made to db and to the list.rnrnAccording to the author, "try-catch" can work, but it'll become very tedious and hard to maintain when the number of statements requiring "try-catch" scales up. So a transaction object should be used here.rnrnHere, although transaction object can work, but it still requires us to write an undo operation for each one, and sometimes it's not trivial to write an undo method.rnrnSo what do I suggest?rnAlthough db operation cannot be immutable, in-memory operation certainly can! If we do immutable programming, we don't need to undo at all!rndon't believe?rnSuppose the friends list is an immutable structure, i.e.rnListrn List cons(Person);rnrnwhere list.cons(p) only returns a new list instance without touching the old instance. (curious how this can be implemented by constant-time and linear storage? I'll give it later)rnrnok, if that's true, what can we do to the addFriend method?rnvoid addFriend(Person p)rnrn List tmp = friends.cons(p);rn addToDb(p);rn friends = tmp;rnrnHa ha, does that solve the undo easily?rnrnrnrnHere is an immutable list implrnrnpublic interface Listrnrn public List cons(Object obj);rn public Object getFirst()throws java.util.NoSuchElementException;rn public List getTail();rn public boolean isEmpty();rn private static final EmptyList singleton = new EmptyList();rn public static List instance()return singleton;rnrnrnclass ConsListrnrn public List cons(Object obj)return new ConsList(obj, this);rn public Object getFirst()return hd;rn public List getTail()return tl;rn public boolean isEmpty()return false;rn private final Object hd;rn private final List tl;rn ConsList(Object hd, List tl)this.hd = hd; this.tl = tl;rnrnclass EmptyListrnrn public List cons(Object obj)return new ConsList(obj, this);rn public Object getFirst()throw new java.util.NoSuchElementException();rn public List getTail()throw new java.util.NoSuchElementException();rn public boolean isEmpty()return true; rn EmptyList() rnrnrn

没有更多推荐了,返回首页