代码之家  ›  专栏  ›  技术社区  ›  Vhaerun

使用访问者模式和界面有什么区别?

  •  4
  • Vhaerun  · 技术社区  · 16 年前

    将访问者设计模式应用于代码与以下方法之间的区别是什么?

    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();
        }
    }
    

    我假设通过使用接口,我们不会真正分离算法。

    5 回复  |  直到 5 年前
        1
  •  3
  •   Benno Richters    16 年前

    两件事:

    • 在您的示例中,您需要两种方法。这个 perfom 以及 setInterface . 对于访问者模式,您只需要一个方法, 佩罗姆 ,通常调用 accept .
    • 如果您需要多个“执行者”,则必须通过 设置接口 方法-针对每个。这使得你的类不可能是不变的。
        2
  •  7
  •   AdamC    16 年前

    有很大的不同。

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

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

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

        3
  •  4
  •   Alex Miller    16 年前

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

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

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

    您可能会问,是否有一个模式允许我们同时执行这两种操作:添加操作或扩展数据结构,而不破坏现有代码。这就是菲利普·韦德勒创造的表达问题。您可以在此找到一些链接以及更多内容:

        4
  •  1
  •   Martin Brown    16 年前

    当您有一个由许多不同类组成的数据结构,并且您有多个算法需要对每个类执行不同的操作时,就会使用访问者模式。在您的示例中,doInterface实现只对一个类型执行一个操作。您唯一要做的就是打印get()的结果,因为您将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)
                // ...
        }
    

    因为它使用对象,所以它将允许具有任何类型的调用方创建只能在运行时显示的错误。访问者通过为数据结构中的每种类型创建一个“visittype”函数来绕过这个问题。然后,数据结构中的类负责知道访问者要调用的函数。映射是由每个数据结构的类执行的,这些类实现一个accept函数,然后调用visitor类。如果访问者上不存在该类型的函数,则会出现编译错误。accept方法如下:

        @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
  •  0
  •   tvanfosson    16 年前

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