<form id="hz9zz"></form>
  • <form id="hz9zz"></form>

      <nobr id="hz9zz"></nobr>

      <form id="hz9zz"></form>

    1. 明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

      Java Tip: 用Reflection完成Visitor模式(轉)

      [摘要]Java Tip: 用Reflection實現Visitor模式概述Visitor模式的常用之處在于,它將對象集合的結構和對集合所執行的操作分離開來。例如,它可以將一個編譯器中的分析邏輯和代碼生成邏...
      Java Tip: 用Reflection實現Visitor模式

      概述

      Visitor模式的常用之處在于,它將對象集合的結構和對集合所執行的操作分離開來。例如,它可以將一個編譯器中的分析邏輯和代碼生成邏輯分離開來。有了這樣的分離,想使用不同的代碼生成器就會很容易。更大的好處還有,其它一些公用程序,如lint,可以在使用分析邏輯的同時免受代碼生成邏輯之累。不幸的是,向集合中增加新的對象往往需要修改已經寫好的Visitor類。本文提出了一種在Java中實現Visitor模式的更靈活的方法:使用Reflection(反射)。

      -------------------------------------------------------------

      集合(Collection)普遍應用于面向對象編程中,但它也經常引發一些和代碼有關的疑問。例如,"如果一個集合存在不同的對象,該如何對它執行操作?"

      一種方法是,對集合中的每個元素進行迭代,然后基于所在的類,對每個元素分別執行對應的操作。這會很難辦,特別是,如果你不知道集合中有什么類型的對象。例如,假設想打印集合中的元素,你可以寫出如下的一個方法(method):

      public void messyPrintCollection(Collection collection) {
       Iterator iterator = collection.iterator()
       while (iterator.hasNext())
      System.out.println(iterator.next().toString())
      }

      這看起來夠簡單的了。它只不過調用了Object.toString()方法,然后打印出對象,對嗎?但如果有一組哈希表怎么辦?事情就會開始變得復雜起來。你必須檢查從集合中返回的對象的類型:

      public void messyPrintCollection(Collection collection) {
       Iterator iterator = collection.iterator()
       while (iterator.hasNext()) {
      Object o = iterator.next();
      if (o instanceof Collection)
       messyPrintCollection((Collection)o);
      else
       System.out.println(o.toString());
       }
      }

      不錯,現在已經解決了嵌套集合的問題,但它需要對象返回String,如果有其它不返回String的對象存在怎么辦?如果想在String對象前后添加引號以及在Float后添加f又該怎么辦?代碼還是越來越復雜:

      public void messyPrintCollection(Collection collection) {
       Iterator iterator = collection.iterator()
       while (iterator.hasNext()) {
      Object o = iterator.next();
      if (o instanceof Collection)
       messyPrintCollection((Collection)o);
      else if (o instanceof String)
       System.out.println("'"+o.toString()+"'");
      else if (o instanceof Float)
       System.out.println(o.toString()+"f");
      else
       System.out.println(o.toString());
       }
      }

      可以看到,事情的復雜度會急劇增長。你當然不想讓一段代碼到處充斥著if-else語句!那怎么避免呢?Visitor模式可以幫你。

      要實現Visitor模式,得為訪問者建立一個Visitor接口,還要為被訪問的集合建立一個Visitable接口。然后,讓具體類實現Visitor和Visitable接口。這兩個接口如下所示:

      public interface Visitor
      {
       public void visitCollection(Collection collection);
       public void visitString(String string);
       public void visitFloat(Float float);
      }

      public interface Visitable
      {
       public void accept(Visitor visitor);
      }

      對于具體的String,可能是這樣:

      public class VisitableString implements Visitable
      {
       private String value;
       public VisitableString(String string) {
      value = string;
       }
       public void accept(Visitor visitor) {
      visitor.visitString(this);
       }
      }

      在accept方法中,對this類型調用正確的visitor方法:

      visitor.visitString(this)

      這樣,就可以如下實現具體的Visitor:

      public class PrintVisitor implements Visitor
      {
       public void visitCollection(Collection collection) {
      Iterator iterator = collection.iterator()
      while (iterator.hasNext()) {
      Object o = iterator.next();
      if (o instanceof Visitable)
       ((Visitable)o).accept(this);
       }

       public void visitString(String string) {
      System.out.println("'"+string+"'");
       }

       public void visitFloat(Float float) {
      System.out.println(float.toString()+"f");
       }
      }

      實現VisitableFloat和VisitableCollection類的時候,它們也是各自調用合適的Visitor方法,所得到的效果和前面那個用了if-else的messyPrintCollection方法一樣,但這里的手法更干凈。在visitCollection()中,調用的是Visitable.accept(this),然后這個調用又返回去調用一個合適的Visitor方法。這被稱做 "雙分派";即,Visitor先調用了Visitable類中的方法,這個方法又回調到Visitor類中。

      雖然通過實現visitor消除了if-else語句,卻也增加了很多額外的代碼。最初的String和Float對象都要用實現了Visitable接口的對象進行包裝。這有點討厭,但一般說來不是問題,因為你可以讓經常被訪問的集合只包含那些實現了Visitable接口的對象。

      但似乎這還是額外的工作。更糟糕的是,當增加一個新的Visitable類型如VisitableInteger時,會發生什么呢?這是Visitor模式的一個重大缺陷。如果想增加一個新的Visitable對象,就必須修改Visitor接口,然后對每一個Visitor實現類中的相應的方法一一實現。你可以用一個帶缺省空操作的Visitor抽象基類來代替接口。那就很象Java GUI中的Adapter類。那個方法的問題在于,它需要占用單繼承;而你往往想保留單繼承,讓它用于其它什么東西,比如繼承StringWriter。那個方法還有限制,它只能夠成功訪問Visitable對象。

      幸運的是,Java可以讓Visitor模式更靈活,使得你可以隨心所欲地增加Visitable對象。怎么做?答案是,使用Reflection。比如,可以設計這樣一個ReflectiveVisitor接口,它只需要一個方法:

      public interface ReflectiveVisitor {
       public void visit(Object o);
      }

      就這樣,很簡單。至于Visitable,還是和前面一樣,我過一會兒再說,F在先用Reflection來實現PrintVisitor:

      public class PrintVisitor implements ReflectiveVisitor {
       public void visitCollection(Collection collection)
       { ... same as above ... }
       public void visitString(String string)
       { ... same as above ... }
       public void visitFloat(Float float)
       { ... same as above ... }

       public void default(Object o)
       {
      System.out.println(o.toString());
       }

       public void visit(Object o) {
      // Class.getName() returns package information as well.
      // This strips off the package information giving us
      // just the class name
      String methodName = o.getClass().getName();
      methodName = "visit"+
       methodName.substring(methodName.lastIndexOf('.')+1);
      // Now we try to invoke the method visit
      try {
       // Get the method visitFoo(Foo foo)
       Method m = getClass().getMethod(methodName,
      new Class[] { o.getClass() });
       // Try to invoke visitFoo(Foo foo)
       m.invoke(this, new Object[] { o });
      } catch (NoSuchMethodException e) {
       // No method, so do the default implementation
       default(o);
      }
       }
      }

      現在不需要Visitable包裝類。僅僅只是調用visit(),請求就會分發到正確的方法上。很不錯的一點是,只要認為適合,visit()就可以分發。這并非必須使用reflection--它可以使用其它完全不同的機制。

      新的PrintVisitor中,有針對Collection,String和Float而寫的方法,但然后它又在catch語句中捕捉所有未處理的類型。你要擴展visit()方法,使得它也能夠處理所有的父類。首先,得增加一個新方法,稱為getMethod(Class c),它返回的是要調用的方法;為了找到這個相匹配的方法,先在類c的所有父類中尋找,然后在類c的所有接口中尋找。

      protected Method getMethod(Class c) {
       Class newc = c;
       Method m = null;
       // Try the superclasses
       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 e) {
       newc = newc.getSuperclass();
      }
       }
       // Try the interfaces.If necessary, you
       // can sort them first to define 'visitable' interface wins
       // in case an object implements more than one.
       if (newc == Object.class) {
      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 e) {}
      }
       }
       if (m == null) {
      try {
       m = thisclass.getMethod("visitObject", new Class[] {Object.class});
      } catch (Exception e) {
      // Can't happen
      }
       }
       return m;
      }

      看起來有些復雜,其實不然。實際上,它只是根據傳進來的類名去尋找相應的方法而已。如果沒找到,就在父類中找;還沒找到,再到接口中找。最后,就拿visitObject()作為缺省。

      注意,為了照顧那些熟悉傳統Visitor模式的讀者,我對方法的名稱采用了傳統的命名方式。但正如你們一些人所注意到的,把所有的方法命名為 "visit" 然后讓參數類型作為區分會更高效。但這樣做的話,你得把主visit(Object o)方法的名字改為dispatch(Object o)之類。否則,就沒有一個缺省方法可用了,你就得在調用visit(Object o)時將類型轉換為Object,以保證visit采用的是正確的調用方式。

      現在可以修改visit()方法,以利用getMethod():

      public void visit(Object object) {
       try {
       Method method = getMethod(getClass(), object.getClass());
       method.invoke(this, new Object[] {object});
       } catch (Exception e) { }
      }

      現在,visitor對象的功能強大多了。你可以傳進任何對象,并且有某個方法處理它。另外一個好處是,還有一個缺省方法visitObject(Object o),它可以捕捉任何未知的對象。再多花點工夫,你還可以寫出一個visitNull()方法。

      我在上面對Visitable接口避而不談自有原因。傳統Visitor模式的另一個好處是,它允許Visitable對象來控制對對象結構的訪問。例如,假設有一個實現了Visitable的TreeNode對象,你可以讓一個accept()方法來遍歷它的左右節點:

      public void accept(Visitor visitor) {
       visitor.visitTreeNode(this);
       visitor.visitTreeNode(leftsubtree);
       visitor.visitTreeNode(rightsubtree);
      }

      這樣,只用對Visitor類再進行一點修改,就可以進行Visitable控制訪問:

      public void visit(Object object) throws Exception
      {
      Method method = getMethod(getClass(), object.getClass());
       method.invoke(this, new Object[] {object});
       if (object instanceof Visitable)
       {
      callAccept((Visitable) object);
       }
      }
      public void callAccept(Visitable visitable) {
       visitable.accept(this);
      }

      如果已經實現了一個Visitable對象結構,可以保留callAccept()方法并使用Visitable控制訪問。如果想在visitor中訪問結構,只需改寫callAccept()方法,使之什么也不做。

      想讓數個不同的訪問者對同一個對象集合進行訪問時,Visitor模式可以發揮它的強大作用。假設已經有一個解釋器,一個中綴寫作器,一個后綴寫作器,一個XML寫作器和一個SQL寫作器,它們都作用在同一個對象集合上。那么,也可以很容易地為相同的對象集合寫出一個前綴寫作器和一個SOAP寫作器。另外,這些寫作器可以正常地和它們所不知道的對象工作;當然,如果愿意,也可以讓它們拋出異常。

      結論
      通過使用Java Reflection,你可以增強Visitor模式,使之具有操作對象結構的強大功能,并在增加新Visitable類型方面提供靈活性。希望你在以后的程序設計中能夠應用這一模式。



      日韩精品一区二区三区高清