【问题标题】:Implementing Visitor using InstanceOf使用 InstanceOf 实现访问者
【发布时间】:2012-12-17 13:04:16
【问题描述】:

我很好掌握访客模式。 但是,我想知道一些事情。

使用访问者模式最重要的动机是在客户端添加涉及特定数据模型的逻辑,而无需检查真实的数据对象类型。用于求解的技术称为 Double-Dispatching。

所以,这里是实现accept() 方法的数据模型的代码sn-p:

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 系列和 instanceof 系列。

任何客户都是:

Visitor visitor = new PrintCarVisitor();
car.accept(visitor);  //no need to know the exact Car's type

但是,既然访问者不遵守开放/封闭原则(因为一个新的数据模型会通过添加自己的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 将是:

public class PrintCarVisitor {
   public void visit(Car c){
     if(c instanceof Ferrari){
       System.out.println("Ferrari");
     }
   }      
}

使用这种替代方法,每个调用者仍会像这样处理数据模型抽象:

new PrintCarVisitor().visit(car); //no need to know the exact data type in client side

先验地,第二种方法很棒,因为它不涉及在实现纯访问者模式期间生成的所有样板。

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

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

2) BoilerPlate 代码在带有instanceofcasting 系列的Visitor 实现类中仍然较重。

它们是否还有其他缺点来解释为什么访问者模式必须使用双重调度,因此不能简单地将 instanceof 系列隔离在一个类中(例如静态 Factory 所做的那样)?

【问题讨论】:

    标签: java design-patterns visitor-pattern open-closed-principle


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

    2. 传统观念通常规定应将instanceof 的使用保留为最后的手段。你为什么要使用instanceof,当你可以让Java的多态性为你做这件事的时候?拥有对象的要点之一就是这种好处。如果您这样做,为什么不避免使用覆盖方法,而只使用instanceof 来确定在类的方法中要做什么,而不是依赖动态调度来处理覆盖方法的情况?

    【讨论】:

    • 我喜欢你的回答。我完全同意instanceof 不是OO,因此应该避免。我的问题不是为了了解最佳实践,而是为了强烈理解为什么双重调度是访问者模式的唯一替代方案。谢谢;)
    【解决方案2】:

    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!");
       }
     }
    

    Here is the source.

    【讨论】:

    • 但是编译时检查的缺乏只发生在我上面描述的 instanceof 替代方案中。那么,为什么要在实现访问者时“保护”涉及“未知”汽车的运行时调用......
    • 因为它使代码可重用和可扩展,而无需更改 Car 或现有其他访问者中的代码。 Car 可以在其accept() 方法中使用PolymorphicDispatcher 来调用实际运行时类型的通用或特定方法。
    • 我想我明白了:使用此解决方案(使用反射机制),无需在数据模型中声明 accept() 方法。这样做会阻止编译时类型检查,但由于反射,运行时添加了类似的类型检查功能。
    • 是的;你可以有一个accept() 方法,它可以神奇地适用于任何类型,即使它们后来被其他人添加。只要从Car 继承某些东西,整个事情就会“正常工作”。
    • @Mik378:是的,同样的想法,但 Xtext 版本的速度足够快,可以在具有许多方法的大型模型上使用,因为它缓存了要调用的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-25
    相关资源
    最近更新 更多