JAVA设计模式—访问者模式(Visitor)

定义:封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

类型:对象行为型模式

类图:

       访问者模式可能是行为类模式中最复杂的一种模式了,但是这不能成为我们不去掌握它的理由。我们首先来看一个简单的例子,代码如下:

[java]  view plain copy
  1. class A {  
  2.     public void method1(){  
  3.         System.out.println("我是A");  
  4.     }  
  5.       
  6.     public void method2(B b){  
  7.         b.showA(this);  
  8.     }  
  9. }  
  10.   
  11. class B {  
  12.     public void showA(A a){  
  13.         a.method1();  
  14.     }  
  15. }  

        我们主要来看一下在类A中,方法method1和方法method2的区别在哪里,方法method1很简单,就是打印出一句“我是A”;方法method2稍微复杂一点,使用类B作为参数,并调用类B的showA方法。再来看一下类B的showA方法,showA方法使用类A作为参数,然后调用类A的method1方法,可以看到,method2方法绕来绕去,无非就是调用了一下自己的method1方法而已,它的运行结果应该也是“我是A”,分析完之后,我们来运行一下这两个方法,并看一下运行结果:

[java]  view plain copy
  1. public class Test {  
  2.     public static void main(String[] args){  
  3.         A a = new A();  
  4.         a.method1();  
  5.         a.method2(new B());  
  6.     }  
  7. }  

运行结果为:

我是A
我是A

       看懂了这个例子,就理解了访问者模式的90%,在例子中,对于类A来说,类B就是一个访问者。但是这个例子并不是访问者模式的全部,虽然直观,但是它的可扩展性比较差,下面我们就来说一下访问者模式的通用实现,通过类图可以看到,在访问者模式中,主要包括下面几个角色:

  •  抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法中的参数定义哪些对象是可以被访问的。
  • 访问者:实现抽象访问者所声明的方法,它影响到访问者访问到一个类后该干什么,要做什么事情。
  • 抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。抽象元素一般有两类方法,一部分是本身的业务逻辑,另外就是允许接收哪类访问者来访问。
  • 元素类:实现抽象元素类所声明的accept方法,通常都是visitor.visit(this),基本上已经形成一种定式了。
  • 结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。

 访问者模式的通用代码实现

[java]  view plain copy
  1. abstract class Element {  
  2.     public abstract void accept(IVisitor visitor);  
  3.     public abstract void doSomething();  
  4. }  
  5.   
  6. interface IVisitor {  
  7.     public void visit(ConcreteElement1 el1);  
  8.     public void visit(ConcreteElement2 el2);  
  9. }  
  10.   
  11. class ConcreteElement1 extends Element {  
  12.     public void doSomething(){  
  13.         System.out.println("这是元素1");  
  14.     }  
  15.       
  16.     public void accept(IVisitor visitor) {  
  17.         visitor.visit(this);  
  18.     }  
  19. }  
  20.   
  21. class ConcreteElement2 extends Element {  
  22.     public void doSomething(){  
  23.         System.out.println("这是元素2");  
  24.     }  
  25.       
  26.     public void accept(IVisitor visitor) {  
  27.         visitor.visit(this);  
  28.     }  
  29. }  
  30. class Visitor implements IVisitor {  
  31.   
  32.     public void visit(ConcreteElement1 el1) {  
  33.         el1.doSomething();  
  34.     }  
  35.       
  36.     public void visit(ConcreteElement2 el2) {  
  37.         el2.doSomething();  
  38.     }  
  39. }  
  40.   
  41. class ObjectStruture {  
  42.     public static List<Element> getList(){  
  43.         List<Element> list = new ArrayList<Element>();  
  44.         Random ran = new Random();  
  45.         for(int i=0; i<10; i++){  
  46.             int a = ran.nextInt(100);  
  47.             if(a>50){  
  48.                 list.add(new ConcreteElement1());  
  49.             }else{  
  50.                 list.add(new ConcreteElement2());  
  51.             }  
  52.         }  
  53.         return list;  
  54.     }  
  55. }  
  56.   
  57. public class Client {  
  58.     public static void main(String[] args){  
  59.         List<Element> list = ObjectStruture.getList();  
  60.         for(Element e: list){  
  61.             e.accept(new Visitor());  
  62.         }  
  63.     }  
  64. }  


双重分派(Double - dispatch)

访问者模式允许你不改变类即可有效的增加其上的操作。为达到这一效果使用了一种称为 双分派(double - dispatch) 的技术。双分派意味着得到执行的操作取决于请求的种类和两个接受者的类型。Accept是一个double - dispatch操作。它的含义决定于两个类型:Visitor的类型和Element的类型。双分派使得访问者可以对每一个类的元素请求不同的操作。

这是Visitor模式的关键所在:得到执行的操作不仅决定于Visitor的类型,还决定于它访问的Element的类型。可以不将操作静态的绑定在Element接口中,而将其安放在一个Visitor中,并使用Accept在运行时进行绑定。扩展Element接口就等于定义一个新的Visitor的子类而不是多个新的Element的子类。


访问者模式的优缺点

  • 访问者模式易于增加新的操作

访问者使得增加依赖于复杂结构对象的构件的操作变得更容易了。仅需增加一个新的访问者即可在一个对象结构上定义一个新操作。相反,如果每个功能都分散在多个类之上的话,定义新的操作时必须修改每一个类。

  • 访问者集中相关的操作而分离无关的操作

相关的行为不是分布在定义该对象结构的各个类上,而是集中在一个访问者中。无关行为却被分别放在它们各自的访问者子类中。这就既简化了这些元素的类,也简化了在这些访问者中定义的算法。所有与它的算法相关的数据结构都可以被隐藏在访问者中。

  • 增加新的ConcreteElement类很困难

Visitor模式使得难以增加新的Element的子类。每添加一个新的ConcreteElement都要在Visitor中添加一个抽象操作,并在每一个ConcreteVisitor类中实现相应的操作。有时可以在Visitor中提供一个缺省的实现,这一实现可以被大多数的ConcreteVisitor继承,但与其说是一种规律还不如说是一种例外。所以在应用访问者模式时考虑的关键的问题是系统的哪个部分会经常变化,是作用于对象结构上的算法呢还是构成该结构的各个对象的类。如果老是有新的ConcreteElement类加进来的话,Visitor类层次将变得难以维护。在这种情况下,直接在构成该结构的类中定义这些操作可能更容易一些。如果Element类层次是稳定的,而你不断的增加操作或修改算法,访问者模式可以帮助你管理这些改动。

  • 通过类层次进行访问

一个迭代器可以通过调用节点对象的特定操作来遍历整个对象结构,同时访问这些对象。但是迭代器不能对具有不同元素类型的对象结构进行操作。 访问者模没有这种限制。它可以访问不具有相同父类的对象。可以对一个Visitor接口中增加任何类型的对象。

  • 累积状态

当访问者访问对象结构中的每一个元素时,它可能会累积状态。

  • 破坏封装

访问者方法假定ConcreteElement接口的功能足够强,足以让访问者进行他们的操作。结果是,该模式常常强迫你提供访问元素内部状态的公共操作,这可能会破坏它的封装性。


 适用性

在下列情况下使用Visitor模式:

  1.  一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。当该结构对象被很多应用共享时,使用Visitor模式让每个应该仅包含需要用到的操作。
  3. 定义结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作比较好。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值