В чем разница между использованием шаблона посетителя и интерфейса? - PullRequest
4 голосов
/ 11 октября 2008

В чем разница между применением шаблона дизайна посетителя к вашему коду и следующим подходом:

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 ]

7 голосов
/ 11 октября 2008

Существует довольно большая разница.

Шаблон посетителя использует интерфейсы, но его цель - иметь возможность выполнять операции с одним или несколькими классами (которые реализуют интерфейс) без необходимости изменения классов. Следовательно, реализация фактически «посещает» класс и делает свое дело без изменения класса.

Интерфейс - это базовая концепция, используемая для предоставления общего API для потенциально разнородной группы классов. Типичным тестом для интерфейса является то, что классы, которые его разделяют, похожи по крайней мере в одном отношении (is-like-a) и в этих случаях могут рассматриваться как таковые.

Вот простой пример википедии, который показывает пару посетителей в java.

4 голосов
/ 12 октября 2008

Самым важным отличием в этих примерах является то, что в случае посетителя вы сохраняете конкретный тип во время компиляции «this». Это позволяет использовать двойную диспетчеризацию, когда вызываемый метод зависит как от конкретного типа данных, так и от реализации посетителя. Двойная диспетчеризация - это особый случай множественной диспетчеризации, когда вызываемый метод зависит от получателя и типов параметров метода. Java, конечно, является одной отправкой, но некоторые другие языки поддерживают множественную отправку.

Основная движущая сила шаблона посетителя заключается в том, что при использовании интерфейсов на конкретных узлах каждая операция, которую необходимо добавить в составную структуру данных, должна изменять каждый узел. Шаблон посетителя использует общий (статический) шаблон на узлах, что упрощает динамическое добавление операций. Недостатком является то, что изменение структуры данных (путем добавления или удаления конкретных узлов) становится более сложным, поскольку затрагиваются все посетители операции.

В общем, этот компромисс лучше подходит, поскольку он чаще расширяет операции над структурой данных, чем изменяет саму структуру данных. Вот мое более длинное письмо о том, как использовать посетителей, и несколько соображений:

Вы могли бы справедливо спросить, существует ли шаблон, который позволяет нам выполнять обе операции: добавлять операции или расширять наши структуры данных, не нарушая существующий код. Это известно как проблема выражения, придуманная Филиппом Уодлером. Вы можете найти некоторые ссылки на это и многое другое здесь:

3 голосов
/ 11 октября 2008

Две вещи:

  • В вашем примере вам нужно два метода. perfom и setInterface. При использовании шаблона посетителя вам потребуется только один метод, perfom, обычно называемый accept.
  • Если вам нужно более одного «исполнителя», вам нужно установить для каждого исполнителя -via метод setInterface Это делает невозможным сделать ваш класс неизменным.
1 голос
/ 20 октября 2008

Шаблон Visitor используется, когда у вас есть структура данных, состоящая из множества разных классов, и у вас есть несколько алгоритмов, которые требуют разных операций для каждого класса. В вашем примере ваша реализация DoInterface делает только одну операцию с одним типом. Единственное, что вы делаете, это печатаете результат getS (), и поскольку вы приводите o к T, вы можете делать это только с классами типа T.

Если вы хотите применить свой интерфейс к типичному классу стиля посетителя, то ваш класс с вашей функцией DoInterface.perform, скорее всего, в конечном итоге получит большой оператор if if 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);
    }

Отчасти проблема с шаблоном Visitor заключается в том, что для того, чтобы по-настоящему оправдать выборку, требуется довольно много кода. Я думаю, именно поэтому многие люди этого не понимают, так как другой код легко отвлекает. Я создал простой пример файловой системы, который, надеюсь, покажет, как использовать посетителя более четко. Он создает композит с несколькими файлами и каталогами, а затем выполняет две операции над иерархией. На практике вы, вероятно, захотите использовать более двух классов данных и две операции для обоснования этого шаблона, но это только пример.

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);
        }
    }
0 голосов
/ 11 октября 2008

Единственное, что я вижу очевидным, это то, что, сохраняя интерфейс, вы делаете это так, что вам нужно выполнить две операции, а не одну, чтобы вызвать его. Я полагаю, что это может иметь смысл, если вы неоднократно собираетесь выполнять одно и то же действие после установки интерфейса, но я думаю, что вы могли бы придерживаться стандартного посетителя и выполнить то же самое.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...