【问题标题】:what is the difference between using the visitor pattern and an interface?使用访问者模式和界面有什么区别?
【发布时间】:2008-10-11 18:02:34
【问题描述】:

将访问者设计模式应用于您的代码与以下方法有什么区别:

interface Dointerface {
    public void perform(Object o);
}

public class T {
    private Dointerface d;
    private String s;

    public String getS() {
            return s;
    }

    public T(String s) {
            this.s = s;
    }

    public void setInterface(Dointerface d) {
            this.d = d;
    }

    public void perform() {
            d.perform(this);
    }

    public static void main(String[] args) {
            T t = new T("Geonline");
            t.setInterface(new Dointerface() {
                    public void perform(Object o) {
                            T a = (T)o;
                            System.out.println(a.getS());
                    }
            });
            t.perform();
    }
}

我假设通过使用接口,我们并没有真正分离算法。

【问题讨论】:

  • 你能澄清你的问题吗? Java 中的访问者模式通常使用定义访问者类型的接口来实现。在您的示例中唯一奇怪的是您存储(简单)访问者然后执行(而不是像命令一样),而不是使用单个方法。
  • 改装下来。问题很困惑。正如其他人回答的那样,这个问题显示了一种访问者模式,并且接口的使用与设计模式正交。
  • @ddaa:不同意。这些问题是由试图理解访问者模式的人提出的。那有什么问题?乐意效劳。但也许问题应该是:为什么访问者模式比这段代码更好?
  • 很抱歉问了这样一个令人困惑的问题。 Bno 所说的正是我要问的。
  • @Bno:这没什么错。但我对“这是否有帮助”的回答是否定的。明确的问题很有帮助,因为使用谷歌搜索它的人能够快速查看这是否回答了他们自己的问题。

标签: java design-patterns coding-style visitor-pattern


【解决方案1】:

有很大的不同。

访问者模式使用接口,但其目的是能够对一个或多个类(实现接口)执行操作,而无需更改类。因此,实现实际上“访问”类并在不修改类的情况下完成其工作。

接口是一个基本概念,用于为可能不同的类组提供通用 API。对接口的典型测试是共享它的类至少在某一方面是相似的(is-like-a),在这些情况下可以这样对待。

Here is a simple example on wikipedia that shows a couple of visitors in java.

【讨论】:

  • 引用的代码几乎完全是Visitor,只是接口是传入,存储,稍后使用。如果接口已传递给 perform() 方法并从那里调用,它将是访问者。我同意接口和访问者是不同的,但这段代码接近访问者。
【解决方案2】:

两件事:

  • 在您的示例中,您需要两种方法。 perfomsetInterface。对于访问者模式,您只需要一种方法,perfom,通常称为accept
  • 如果您需要多个“表演者”,则必须通过setInterface 方法为每个表演者设置表演者。这使得你的类不可能不可变。

【讨论】:

    【解决方案3】:

    这些示例中最重要的区别是,在访问者案例中,您保留了“this”的编译时具体类型。这允许您使用双重分派,其中要调用的方法取决于具体数据类型和访问者实现。双重分派只是多重分派的一种特殊情况,其中调用的方法取决于接收者和方法参数的类型。 Java 当然是单分派,但其他一些语言支持多分派。

    访问者模式背后的基本驱动力是,通过在具体节点上使用接口,需要添加到复合数据结构中的每个操作都必须更改每个节点。访问者模式在节点上使用通用(静态)模式,因此动态添加操作很容易。缺点是修改数据结构(通过添加或删除具体节点)变得更加困难,因为所有操作访问者都会受到影响。

    一般来说,这种权衡是更好的匹配,因为在数据结构上扩展操作比更改数据结构本身更频繁。这是我关于如何使用访问者和一系列注意事项的较长文章:

    您可能会问是否有一种模式可以让我们同时做到这两种:添加操作或扩展我们的数据结构而不破坏现有代码。这被称为 Philip Wadler 创造的表达问题。您可以在此处找到有关此内容的一些链接以及更多链接:

    【讨论】:

      【解决方案4】:

      当您的数据结构由许多不同的类组成并且您有多种算法需要对每个类进行不同的操作时,就会使用访问者模式。在您的示例中,您的 DoInterface 实现仅对一种类型执行一项操作。您唯一要做的就是打印 getS() 的结果,因为您将 o 转换为 T,您只能对 T 类型的类执行此操作。

      如果您想将您的界面应用到一个典型的访问者样式类,您的带有 DoInterface.perform 函数的类很可能会在其中包含一个大的 if else if 语句,如下所示:

          public void visit(Object o) {
              if (o instanceof File)
                  visitFile((File)o);
              else if (o instanceof Directory)
                  visitDirectory((Directory)o);
              else if (o instanceof X)
                  // ...
          }
      

      因为它使用 Object ,所以它允许任何类型的调用者创建只会在运行时出现的错误。访问者通过为数据结构中的每种类型创建一个“visitType”函数来解决这个问题。然后数据结构中的类负责了解访问者上的哪个函数要调用。映射由数据结构的每个类执行一个接受函数,然后回调访问者类。如果访问者上不存在该类型的函数,则会出现编译错误。接受方法如下所示:

          @Override
          public void accept(FileSystemVisitor v) {
              v.visitFile(this);
          }
      

      访问者模式的部分问题在于,要在示例中真正做到公正需要大量代码。我认为这就是为什么很多人不明白它的原因,因为很容易被其他代码分心。我创建了一个简单的文件系统示例,希望能更清楚地展示如何使用访问者。它创建一个包含一些文件和目录的组合,然后对层次结构执行两个操作。在实践中,您可能需要两个以上的数据类和两个操作来证明这种模式的合理性,但这只是一个示例。

      public class VisitorSample {
          //
              public abstract class FileSystemItem {
                  public abstract String getName();
                  public abstract int getSize();
                  public abstract void accept(FileSystemVisitor v);
              }
          //  
              public abstract class FileSystemItemContainer extends FileSystemItem {
                  protected java.util.ArrayList<FileSystemItem> _list = new java.util.ArrayList<FileSystemItem>();
          //              
                  public void addItem(FileSystemItem item)
                  {
                      _list.add(item);
                  }
          //
                  public FileSystemItem getItem(int i)
                  {
                      return _list.get(i);
                  }
          //          
                  public int getCount() {
                      return _list.size();
                  }
          //      
                  public abstract void accept(FileSystemVisitor v);
                  public abstract String getName();
                  public abstract int getSize();
              }
          //  
              public class File extends FileSystemItem {
          //
                  public String _name;
                  public int _size;
          //      
                  public File(String name, int size) {
                      _name = name;
                      _size = size;
                  }
          //      
                  @Override
                  public void accept(FileSystemVisitor v) {
                      v.visitFile(this);
                  }
          //
                  @Override
                  public String getName() {
                      return _name;
                  }
          //
                  @Override
                  public int getSize() {
                      return _size;
                  }
              }
          //  
              public class Directory extends FileSystemItemContainer {
          //
                  private String _name;
          //      
                  public Directory(String name) {
                      _name = name;
                  }
          //      
                  @Override
                  public void accept(FileSystemVisitor v) {
                      v.visitDirectory(this);
                  }
          //
                  @Override
                  public String getName() {
                      return _name;
                  }
          //
                  @Override
                  public int getSize() {
                      int size = 0;
                      for (int i = 0; i < _list.size(); i++)
                      {
                          size += _list.get(i).getSize();
                      }
                      return size;
                  }       
              }
          //  
              public abstract class FileSystemVisitor {
          //      
                  public void visitFile(File f) { }
                  public void visitDirectory(Directory d) { }
          //
                  public void vistChildren(FileSystemItemContainer c) {
                      for (int i = 0; i < c.getCount(); i++)
                      {
                          c.getItem(i).accept(this);
                      }
                  }
              }
          //  
              public class ListingVisitor extends FileSystemVisitor {
          //      
                  private int _indent = 0;
          //      
                  @Override
                  public void visitFile(File f) {
                      for (int i = 0; i < _indent; i++)
                          System.out.print(" ");
                      System.out.print("~");
                      System.out.print(f.getName());
                      System.out.print(":");
                      System.out.println(f.getSize());
                  }
          //  
                  @Override
                  public void visitDirectory(Directory d) {
                      for (int i = 0; i < _indent; i++)
                          System.out.print(" ");  
                      System.out.print("\\");
                      System.out.print(d.getName());
                      System.out.println("\\");
          //          
                      _indent += 3;
                      vistChildren(d);
                      _indent -= 3;
                  }
              }
          //  
              public class XmlVisitor extends FileSystemVisitor {
          //      
                  private int _indent = 0;
          //      
                  @Override
                  public void visitFile(File f) {
                      for (int i = 0; i < _indent; i++)
                          System.out.print(" ");
                      System.out.print("<file name=\"");
                      System.out.print(f.getName());
                      System.out.print("\" size=\"");
                      System.out.print(f.getSize());
                      System.out.println("\" />");
                  }
          //  
                  @Override
                  public void visitDirectory(Directory d) {
                      for (int i = 0; i < _indent; i++)
                          System.out.print(" ");
                      System.out.print("<directory name=\"");
                      System.out.print(d.getName());
                      System.out.print("\" size=\"");
                      System.out.print(d.getSize());
                      System.out.println("\">");
          //          
                      _indent += 4;
                      vistChildren(d);
                      _indent -= 4;
          //          
                      for (int i = 0; i < _indent; i++)
                          System.out.print(" ");
                      System.out.println("</directory>");
                  }
              }
          //  
              public static void main(String[] args) {
                  VisitorSample s = new VisitorSample();
          //      
                  Directory root = s.new Directory("root");
                  root.addItem(s.new File("FileA", 163));
                  root.addItem(s.new File("FileB", 760));
                  Directory sub = s.new Directory("sub");
                  root.addItem(sub);
                  sub.addItem(s.new File("FileC", 401));
                  sub.addItem(s.new File("FileD", 543));
                  Directory subB = s.new Directory("subB");
                  root.addItem(subB);
                  subB.addItem(s.new File("FileE", 928));
                  subB.addItem(s.new File("FileF", 238));
          //      
                  XmlVisitor xmlVisitor = s.new XmlVisitor();
                  root.accept(xmlVisitor);
          //      
                  ListingVisitor listing = s.new ListingVisitor();
                  root.accept(listing);
              }
          }
      

      【讨论】:

        【解决方案5】:

        我看到的唯一显而易见的事情是,通过存储接口,您可以创建它,因此您必须执行两个操作而不是一个来调用它。我认为,如果您在设置界面后重复执行相同的操作,这可能是有道理的,但我认为您可以坚持使用标准的访问者并完成相同的事情。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-07-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-02
          • 1970-01-01
          • 2013-05-15
          相关资源
          最近更新 更多