根据我发现的代码,似乎访问者需要知道访问对象的结构并调用所需的子代。在某些情况下,即使访问类被修改,访问者仍希望继续工作,这似乎有点笨拙。
我想真正的问题是:这是一种模式,其中枚举是由访问过的代码而不是访问者代码完成的吗?
答案 0 :(得分:12)
访问者对象 需要知道它访问的内容的结构。不过那没关系。您应该为访问者知道如何访问的每种类型的事物编写专门的访问操作。这允许访问者决定它真正想要访问多少,以及按什么顺序。
假设你有一棵树。一个访问者可能会进行预订遍历,一个可能会执行有序遍历,而另一个访问者可能只对叶节点执行操作。访问者类可以执行所有这些操作,而无需对树类进行任何更改。
访问者知道结构,但这并不一定意味着访问者执行的操作知道所有结构。您可以将访问者与命令组合在一起。为访问者对象提供一个命令对象,访问者将在它访问的每个事物上调用该命令。
如果您想进行简单的操作并让集合为您提供每个项目,那么您希望集合为自己提供迭代器。在迭代器为你提供的每件事上调用你的函数。
如果要以各种顺序迭代树的节点,那么树将需要提供多个迭代器。如果要按树不支持的顺序处理节点,则需要修改树类。
答案 1 :(得分:3)
是。被访问的对象可以进行枚举(即调用所需的子进程)。这仍称为“访问者”模式(事实上,设计模式的第一个访问者样本就是这样做的)。我的假设示例摘录:
public void accept(Visitor visitor) {
for (Node n : children) {
n.accept(visitor);
}
}
注意:访问孩子时,我们不能说visitor.visit(n);
。这是因为Java不会动态选择方法(基于其参数的运行时类),而是静态选择方法(通过其参数的编译时类型)。
答案 2 :(得分:2)
简而言之,我认为访客模式与枚举的方式是正交的。它可以以任何方式完成,也可以根本不进行枚举。
我认为访问者必须知道访问结构所包含的元素。想知道汽车由车轮和发动机组成。我想,要知道它们的组合方式是不必要的。考虑以下示例。 Insider知道访问过的对象结构并自己执行枚举。局外人不知道它并将枚举委托给被访问对象。
interface Visitable {
void accept(Visitor visitor);
}
class WorkingRoom implements Visitable {
public int number;
WorkingRoom(int number) {
this.number = number;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class BossRoom implements Visitable {
public String bossName;
BossRoom(String bossName) {
this.bossName = bossName;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
interface Visitor{
void visit(WorkingRoom workingRoom);
void visit(BossRoom bossRoom);
void visit(Office office);
}
class Office implements Visitable{
public Visitable[] firstFloor;
public Visitable[] secondFloor;
public Visitable ceoRoom;
public Office(){
firstFloor = new Visitable[]{ new WorkingRoom(101),
new WorkingRoom(102),
new BossRoom("Jeff Atwood"),
new WorkingRoom(103)};
secondFloor = new Visitable[]{ new WorkingRoom(201),
new WorkingRoom(202),
new BossRoom("Joel Spolsky")};
ceoRoom = new BossRoom("Bill Gates");
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void showMeTheOffice(Visitor visitor, boolean sayPlease) {
// Office manager decides the order in which rooms are visited
for(int i=secondFloor.length-1; i >= 0; i--){
secondFloor[i].accept(visitor);
}
if (sayPlease){
ceoRoom.accept(visitor);
}
for (int i = 0; i < firstFloor.length; i++) {
firstFloor[i].accept(visitor);
}
}
}
class Insider implements Visitor{
public void visit(WorkingRoom workingRoom) {
System.out.println("I> This is working room #"+workingRoom.number);
}
public void visit(BossRoom bossRoom) {
System.out.println("I> Hi, "+bossRoom.bossName);
}
public void visit(Office office) {
// I know about office structure, so I'll just go to the 1st floor
for(int i=0;i<office.firstFloor.length;i++){
office.firstFloor[i].accept(this);
}
}
}
class Outsider implements Visitor{
public void visit(Office office) {
// I do not know about office structure, but I know they have a
// nice office manager
// I'll just ask to show me the office
office.showMeTheOffice(this, true);
}
public void visit(WorkingRoom workingRoom) {
System.out.println("O> Wow, room #"+workingRoom.number);
}
public void visit(BossRoom bossRoom) {
System.out.println("O> Oh, look, this is "+bossRoom.bossName);
}
}
public class Main{
public static void main(String[] args) {
Office office = new Office(); // visited structure
// visitor who knows about office structure
Insider employee = new Insider();
office.accept(employee);
System.out.println();
// visitor who does not know about exact office structure
// but knows something else
Outsider candidate = new Outsider();
office.accept(candidate);
// no enumeration at all, but still a visitor pattern
Visitable v = new BossRoom("Linus Torvalds");
v.accept(candidate);
}
}
我有一个广泛使用访问者模式的项目,根本没有任何枚举。我们有基本接口Field和许多实现它的类,比如StringField,NumberField等。我们经常根据字段类型做不同的事情,例如以不同的方式呈现它,从DB加载,导出到xml等等我们可以在Field接口中定义方法,但这会使它与项目的每个特征相结合 - 穷人必须知道导出,导入,渲染到html和rtf等等。我们也可以使用instanceof但是可能的实现Field接口的类随着时间的推移而发生了变化,可以添加新的字段类型并忘记添加
else if (field instanceof NewlyAddedFieldType) {...}
某处。所以我们决定使用访客模式,就像
Visitor v = new XMLExportVisitor(outputStream);
field.accept(v);
因为任何Field实现都需要方法
void accept(FieldVisitor visitor)
然后如果我添加Field接口的新实现,我必须以某种方式实现它。通常是
visitor.visit(this);
这是一个新添加的类。这迫使我添加
void visit(NewlyAddedClass visited);
到FieldVisitor接口,这使我在我们已经拥有的每个FieldVisitor实现中实现它。所以,如果我忘记做某事 - 我会遇到编译器错误。 在这种情况下,枚举(如果有的话)是在访问过的结构和访问者之外完成的。但我仍然认为它是访客模式的有效案例。 它实现起来有点困难,但使用起来更简单,更安全。
答案 3 :(得分:2)
Hierarchical Visitor Pattern解释了一种不同的方法,即添加进入和离开级别的事件。关联的discussion page表示迭代在访问者或容器内的参数。它包含了一个使用外部迭代器的建议,如果你有一个常规的树并需要以不同的方式迭代,这对我来说很有意义。
回顾我的oofRep visitor,它有一系列不同的类可供访问,并在以下方法中进行迭代:
void
oofRepVisitor::VisitViewHeaders(oofRepBandList& inBands)
{
VisitBandList(inBands);
}
void
oofRepVisitor::VisitBandList(oofRepBandList& inBands)
{
EnterLevel();
const unsigned long numBands = inBands.count();
for (unsigned long i=0; i<numBands; i++) {
oofRepBand* theBand = inBands.value(i);
assert(theBand);
VisitTypedBand(theBand);
}
LeaveLevel();
}
带覆盖
void
OOF_repXMLlayoutVisitor::VisitViewHeaders(oofRepBandList& inBands)
{
oofRepStreamEnv::out() << mIdentities.getIndentString();
if (inBands.keepTogether())
oofRepStreamEnv::out() << "<header>\n";
else // default is ON, and simplifies XML
oofRepStreamEnv::out() << "<header keepTogether='false'>\n";
VisitBandList(inBands);
oofRepStreamEnv::out()
<< mIdentities.getIndentString()
<< "</header>\n";
}
答案 4 :(得分:0)