Design Pattern: Visitor 模式
 

2009-09-18 来源:riabook.cn

 

在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 结构类图如下:

Visitor

Java World中有一篇文章,提到可以利用reflection来改进使用访问者模式时的弹性,有兴趣的可以进一步参考一下Reflect on the Visitor design pattern


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织