В чем разница между применением шаблона дизайна посетителя к вашему коду и следующим подходом:
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();
}
}
Я предполагаю, что, используя интерфейсы, мы не разделяем алгоритм.
Моддинг вниз. Вопрос запутанный. Как ответили другие, вопрос показывает вид шаблона посетителя, а использование интерфейсов ортогонально шаблонам проектирования.
@ddaa: Не согласен. Вопросы задает кто-то, кто пытается понять структуру посетителей. Что в этом плохого? Рад помочь. Но, возможно, следовало задать вопрос: почему шаблон посетителя лучше этого кода?
Мне очень жаль, что я задал такой запутанный вопрос. Я действительно хотел спросить то, что сказал Бно.
@Bno: В этом нет ничего плохого. Но мой ответ на вопрос «Было ли это полезным?» - нет. Четкие вопросы полезны, потому что тот, кто погуглил, может быстро увидеть, отвечает ли он на его собственный вопрос.




Единственное, что мне кажется очевидным, - это то, что, сохраняя интерфейс, вы делаете так, чтобы для его вызова вам приходилось выполнять две операции, а не одну. Я полагаю, что это может иметь смысл, если вы собираетесь многократно выполнять одно и то же действие после настройки интерфейса, но я думаю, что вы могли бы придерживаться стандартного посетителя и выполнять то же самое.
Есть довольно большая разница.
Шаблон посетителя использует интерфейсы, но его цель - иметь возможность выполнять операцию с одним или несколькими классами (которые реализуют интерфейс) без необходимости изменять классы. Следовательно, реализация фактически «посещает» класс и выполняет свою работу без изменения класса.
Интерфейс - это базовая концепция, используемая для предоставления общего API потенциально разнообразной группе классов. Типичный тест интерфейса состоит в том, что классы, которые разделяют его, похожи по крайней мере в одном отношении (is-like-a), и в этих случаях их можно рассматривать как таковые.
Вот простой пример из Википедии, который показывает пару посетителей в java.
Указанный код почти полностью соответствует Visitor, за исключением того, что интерфейс передается, сохраняется, а затем используется позже. Если бы интерфейс был передан в метод perform () и вызван оттуда, это был бы Visitor. Я согласен, что интерфейсы и Visitor разные, но этот код близок к Visitor.
Две вещи:
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);
}
}
Не могли бы вы уточнить свой вопрос? Шаблон посетителя в Java обычно реализуется с использованием интерфейса для определения типа посетителя. Единственная любопытная вещь в вашем примере заключается в том, что вы сохраняете (простого) посетителя, а затем выполняете (скорее, как команду), а не используете один метод.