访问者设计模式应用于代码与以下方法有什么区别:
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();
}
}
我假设通过使用接口,我们并没有真正分离算法。
有很大的不同。
访问者模式使用接口,但其用途是能够对一个或多个类(实现接口(执行操作,而无需更改类。 因此,实现实际上"访问"类并在不修改类的情况下执行其操作。
接口是一个基本概念,用于为可能不同的类组提供通用 API。 接口的典型测试是共享它的类至少在一个方面(is-like-a(是相似的,并且在这些情况下可以被视为相似。
这是维基百科上的一个简单示例,显示了Java中的几个访问者。
两件事:
- 在您的示例中,您需要两种方法。
perfom
和setInterface
.使用访客模式,您只需要一种方法,即perfom
,通常称为accept
。 - 如果您需要多个"表演者",则必须通过
setInterface
方法为每个执行者设置表演者。这使得无法使类不可变。
这些示例中最重要的区别是,在访问者的情况下,您保留了编译时的具体类型"this"。 这允许您使用双重调度,其中要调用的方法取决于具体数据类型和访问者实现。 双重调度只是多重调度的一种特例,其中调用的方法取决于接收方和方法的参数类型。 Java当然是单一调度,但其他一些语言支持多调度。
访客模式背后的基本驱动力是,通过在具体节点上使用接口,需要添加到复合数据结构中的每个操作都必须更改每个节点。 访客模式在节点上使用通用(静态(模式,因此动态添加操作很容易。 缺点是修改数据结构(通过添加或删除具体节点(变得更加困难,因为所有操作访问者都会受到影响。
通常,此 trade=off 是更好的匹配,因为通过数据结构扩展操作比更改数据结构本身更频繁。 这是我关于如何使用访问者和一系列注意事项的较长文章:
- http://tech.puredanger.com/2007/07/16/visitor/
你可能会公平地问,是否有一种模式允许我们同时做这两件事:添加操作或扩展我们的数据结构,而不会破坏现有代码。 这被称为菲利普·瓦德勒(Philip Wadler(创造的表达问题。 您可以在此处找到有关此内容以及更多内容的链接:
- http://tech.puredanger.com/presentations/design-patterns-reconsidered
当您的数据结构由许多不同的类组成,并且您有多个算法需要对每个类进行不同的操作时,将使用访问者模式。在您的示例中,您的 DoInterface 实现只对一种类型执行一个操作。你唯一要做的就是打印getS((的结果,因为你将o转换为T,所以你只能对T类型的类执行此操作。
如果你想将你的界面应用于一个典型的访问者风格的类,那么你就是你的DoInterface.perform函数的类,最终可能会得到一个大的ifelse 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 类。如果访问者上不存在该类型的函数,则会出现编译错误。接受方法如下所示:
@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);
}
}
我看到的唯一显而易见的是,通过存储接口,您必须执行两个操作而不是一个操作来调用它。 我想如果您在设置界面后重复执行相同的操作,这可能是有意义的,但我认为您可以坚持使用标准访问者并完成同样的事情。