Java 使用InstanceOf实现访问者

Java 使用InstanceOf实现访问者,java,design-patterns,visitor,open-closed-principle,Java,Design Patterns,Visitor,Open Closed Principle,我很好地掌握了访客模式。 然而,我想知道一些事情 使用访问者模式的最重要动机是在客户端添加涉及特定数据模型的逻辑,而不需要检查实际的数据对象类型。用于解决此问题的技术称为双重调度 下面是实现accept()方法的数据模型的代码片段: public class Ferrari extends Car { //...... @Override public void accept(Visitor v){ v.visit(this); } } 这里有一

我很好地掌握了访客模式。 然而,我想知道一些事情

使用访问者模式的最重要动机是在客户端添加涉及特定数据模型的逻辑,而不需要检查实际的数据对象类型。用于解决此问题的技术称为双重调度

下面是实现
accept()
方法的数据模型的代码片段:

public class Ferrari extends Car {
    //......
    @Override
    public void accept(Visitor v){
      v.visit(this);
    }
  }
这里有一个
PrintCarVisitor
实现
Visitor
接口:

public class PrintCarVisitor implements Visitor {
  //...
  @Override
  public void visit(Ferrari c){
    System.out.println("Ferrari");
  }
}
因此,不需要
if/else
系列和
系列实例

任何客户将:

Visitor visitor = new PrintCarVisitor();
car.accept(visitor);  //no need to know the exact Car's type
public class PrintCarVisitor {
   public void visit(Car c){
     if(c instanceof Ferrari){
       System.out.println("Ferrari");
     }
   }      
}
然而,既然Visitor没有保持打开/关闭原则(因为一个新的数据模型通过添加自己的
visit
方法来破坏类),我们为什么还要麻烦双重分派呢

我们不能在访问者实现中隔离
if/else
系列吗

有了这个假设的替代方案,这部分代码将消失:

 public class Ferrari extends Car {
    //This method is not needed anymore with this alternative
    @Override
    public void accept(Visitor v){
      v.visit(this);
    }
  }
PrintCarVisitor
将是:

Visitor visitor = new PrintCarVisitor();
car.accept(visitor);  //no need to know the exact Car's type
public class PrintCarVisitor {
   public void visit(Car c){
     if(c instanceof Ferrari){
       System.out.println("Ferrari");
     }
   }      
}
在这种情况下,每个调用方仍然会像这样处理数据模型抽象:

newprintcarvisitor().visit(汽车)//无需知道客户端的确切数据类型

从本质上讲,第二种方法很好,因为它不涉及纯访问者模式实现过程中生成的所有样板文件

我认为这种方法有两个缺点:

1) 无法保证(如
Visitor
界面强制)任何使用过的访问者都会处理与当前处理的
Car
对应的方法

2) 样板代码在Visitor实现类中仍然较重,包括
instanceof
casting
系列

它们是否有其他缺陷,可以解释为什么访问者模式必须使用双重分派,因此不能简单地将类中的
系列的
实例隔离开来(例如静态
工厂
所做的)

  • 如果你这样做,你就不再有访客了,你基本上有了某种处理器。您的代码将只是一个列表迭代,每次通过循环时,您都会将以前使用的
    accept
    传递给处理器,处理器就是正式的访问者。在某种意义上,你是在颠倒范式,而不是访问者访问被访问者;被访问的对象将成为访问者,因为它最初会传递给工作人员。你可以做到;不过你不会说它是访客

  • 传统智慧通常规定,使用
    instanceof
    应保留为最后手段。当您可以让Java的多态性为您做这件事时,为什么要使用
    instanceof
    ?拥有对象的一个好处就是这个好处。如果这样做,为什么不避免重写方法,而只是使用
    instanceof
    来确定在重写方法的情况下,在类的方法中要做什么,而不是依赖于动态分派


  • Xtext项目也有同样的问题,他们创建了一个助手类
    PolymorphicDispatcher
    。简而言之,
    PolymorphicDispatcher
    在运行时执行编译器在编译时执行的操作:找到与一组参数最匹配的方法并调用它

    因此,您的访客看起来像:

     public class PrintCarVisitor {
       public void visit(Car c){
          System.out.println("Just a car");
       }
       public void visit(Ferrari c){
          System.out.println("A Ferrari!");
       }
     }
    

    我喜欢你的回答。我完全同意
    instanceof
    不是OO,因此应该避免。我的问题不是要了解最佳实践,而是要强烈理解为什么双重调度是访客模式的唯一选择。谢谢;)但是编译时检查的缺乏只发生在我上面描述的instanceof alternative中。那么,为什么在实现Visitor时要“保护”涉及“未知”Car的运行时调用呢?因为它使代码可以重用和扩展,而无需更改
    Car
    或现有的其他访问者中的代码
    Car
    可以在它的
    accept()
    方法中使用
    PolymorphicDispatcher
    来调用实际运行时类型的通用或特定方法。我想我明白了:使用此解决方案(使用反射机制),无需在数据模型中声明
    accept()
    方法。这样做将阻止编译时类型检查,但由于反射,运行时添加了类似的类型检查功能;您可以有一个
    accept()
    方法,该方法可以神奇地适用于任何类型,即使稍后由其他人添加。只要有东西继承自
    汽车
    ,整个事情就会“正常工作”。@Mik378:是的,同样的想法,但Xtext版本足够快,可以在具有许多方法的大型模型上使用,因为它缓存了要调用的方法。