設計模式之(二十一)訪問者模式Visitor

在Java中所有的物件都繼承自Object物件,這樣作的優點之一,就是使得一些集合物件的資料結構容易管理,例如您可以將任何型態的物件放入Vector中。

然而現在有個問題是,如果您的集合(connection)物件中不僅儲存一種型態的物件,如果想要對這些物件作出一些個別化的操作,首要條件就是要知道該物件的型態,使用 instanceof 似乎是個不錯的方式,在程式簡單的情況下,也許您會這麼作:
 public class ElementA { 
    // some implementing 
 } 
 
 public class ElementB { 
    // some implementing 
 } 

 public class ElementC { 
    // some implementing 
 } 

 // ...... 

    Iterator iterator = arrayList.iterator() 
    while (iterator.hasNext()) { 
        if (o instanceof ElementA) 
        (ElementA) o.operationA(); 
        else if (o instanceof ElementB) 
         (ElementB) o.operationB(); 
      else if (o instanceof ElementC) 
         (ElementC) o.operationC(); 
      else 
         System.out.println(
                  "Sorry! I don't know who you are! " 
                   + o.toString()); 
        //....
    }

    //....
 
這麼作並不是不可以,只是將來的擴充性不大,如果今天您想要一次改變對每一種類型物件的操作,您必須修改很多地方。

從物件自身的角度來想好了,物件在一個個的房子中,物件說:“不要在房子外費盡心思判斷了,即然您不知道我是誰,那麼您就進來訪問我好了,我告訴您我是誰,這麼一來您就知道如何操作我了!”

用程式來實現上面這個描述:
  • IElement.java
public interface IElement { 
    public void accept(IVisitor visitor); 
} 

  • ElementA.java
public class ElementA implements IElement { 
    public void accept(IVisitor visitor) { 
        visitor.visit(this); 
    }

    public void operationA() { 
        System.out.println(
              "do A's job....such-and-such...."); 
    } 
} 

  • ElementB.java
public class ElementB implements IElement { 
    public void accept(IVisitor visitor) { 
        visitor.visit(this); 
    }

    public void operationB() { 
        System.out.println(
           "do B's job....such-and-such...."); 
    }
} 

  • ElementC.java
public class ElementC implements IElement { 
    public void accept(IVisitor visitor) { 
        visitor.visit(this); 
    }

    public void operationC() { 
        System.out.println(
            "do C's job....such-and-such...."); 
    } 
} 

  • IVisitor.java
public interface IVisitor { 
    public void visit(ElementA element); 
    public void visit(ElementB element); 
    public void visit(ElementC element); 
}  

  • VisitorA.java
public class VisitorA implements IVisitor { 
    public void visit(ElementA element) { 
        element.operationA(); 
    }

    public void visit(ElementB element) { 
        element.operationB(); 
    }

    public void visit(ElementC element) { 
        element.operationC(); 
    } 
}  

  • Main.java
public class Main { 
    public static void main(String[] args) { 
        // know nothing about their type 
        // after storing them into Element array 
        IElement[] list = {new ElementA(), 
                           new ElementB(), 
                           new ElementC()}; 

        IVisitor visitor = new VisitorA();

        for (int i=0; i < list.length; i++) 
            list[i].accept(visitor); 
    } 
} 

Visitor訪問是基於overload來完成,對於每一個實現IElement的物件來說,它接受IVisitor來訪問它,在accept()方法 中,IVisitor使用正確的方法來訪問IElement(顯然的,這麼部份可以靠不同的函式名稱,或是overload來達成),並在visit() 中對IElement作出對應的操作,如果您今天想要換掉每一個IElement的操作,只要更換IVisitor類型的物件就可以了,也就是這行:
 // IVisitor visitor = new VisitorA(); 
 // 換掉一個IVisitor,就可以換掉所有的操作
 // 不用修改多個地方
 IVisitor visitor = new VisitorB(); 
 

舉個實際的例子,假設VisitorA只是個懶惰的推銷員好了,今天有一個比較勤快的推銷員VisitorB,在訪問過IElement之後,會對 IElement作出更多的操作,要在程式中實現VisitorB,只要增加一個VisitorB類別就可以了:
  • VisitorB.java
public class VisitorB implements IVisitor { 
    public void visit(ElementA element) { 
        System.out.println("VisitorB is a hard worker...."); 
        element.operationA(); 
        System.out.println(
            "I want to do some extra work on A...."); 
    }

    public void visit(ElementB element) { 
        System.out.println("VisitorB is a hard worker...."); 
        element.operationB(); 
        System.out.println(
                   "I want to do some extra work on B...."); 
    }

    public void visit(ElementC element) { 
        System.out.println("VisitorB is a hard worker...."); 
        element.operationC(); 
        System.out.println(
                  "I want to do some extra work on C...."); 
    } 
} 

改一下Main來示範:
  • Main.java
public class Main { 
    public static void main(String[] args) { 
        IElement[] list = {new ElementA(), 
                           new ElementB(), 
                           new ElementC()}; 

        System.out.println("visitorA is coming......."); 
        IVisitor visitorA = new VisitorA(); 
        for (int i=0; i < list.length; i++) 
           list[i].accept(visitorA);

        System.out.println("\nvisitorB is coming......."); 
        IVisitor visitorB = new VisitorB(); 
        for (int i=0; i < list.length; i++) 
            list[i].accept(visitorB); 
    } 
} 

在範例中的System.out.println();只是個示意,它也可能是您對IElement的額外方法的直接調用。 

Visitor模式的 UML 結構類圖如下:


Java World中有一篇文章,提到可以利用reflection來改進使用訪問者模式時的彈性,有興趣的可以進一步參考一下Reflect on the Visitor design pattern
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章