В чем разница между использованием шаблона посетителя и интерфейса?

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

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 обычно реализуется с использованием интерфейса для определения типа посетителя. Единственная любопытная вещь в вашем примере заключается в том, что вы сохраняете (простого) посетителя, а затем выполняете (скорее, как команду), а не используете один метод.

Tom Hawtin - tackline 11.10.2008 22:07

Моддинг вниз. Вопрос запутанный. Как ответили другие, вопрос показывает вид шаблона посетителя, а использование интерфейсов ортогонально шаблонам проектирования.

ddaa 11.10.2008 23:11

@ddaa: Не согласен. Вопросы задает кто-то, кто пытается понять структуру посетителей. Что в этом плохого? Рад помочь. Но, возможно, следовало задать вопрос: почему шаблон посетителя лучше этого кода?

Benno Richters 11.10.2008 23:24

Мне очень жаль, что я задал такой запутанный вопрос. Я действительно хотел спросить то, что сказал Бно.

Vhaerun 11.10.2008 23:54

@Bno: В этом нет ничего плохого. Но мой ответ на вопрос «Было ли это полезным?» - нет. Четкие вопросы полезны, потому что тот, кто погуглил, может быстро увидеть, отвечает ли он на его собственный вопрос.

ddaa 16.10.2008 21:58
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
5
1 729
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

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

Есть довольно большая разница.

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

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

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

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

tvanfosson 11.10.2008 22:57
Ответ принят как подходящий

Две вещи:

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

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

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

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

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

Шаблон посетителя используется, когда у вас есть структура данных, состоящая из множества разных классов, и у вас есть несколько алгоритмов, требующих разных операций для каждого класса. В вашем примере реализация 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» для каждого типа в структуре данных. Затем классы в структуре данных отвечают за знание того, какую функцию посетителя следует вызвать. Сопоставление выполняется каждым из классов структуры данных, реализующим функцию 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);
        }
    }

Другие вопросы по теме