根据我找到的代码,似乎需要访问者知道访问对象的结构并调用所需的子对象。在某些情况下,即使访问的类被修改,访问者仍希望继续工作,这似乎有点笨拙。
我想真正的问题是:它们是由访问代码而不是访问代码完成枚举的模式吗?
根据我找到的代码,似乎需要访问者知道访问对象的结构并调用所需的子对象。在某些情况下,即使访问的类被修改,访问者仍希望继续工作,这似乎有点笨拙。
我想真正的问题是:它们是由访问代码而不是访问代码完成枚举的模式吗?
访问者对象需要知道它访问的事物的结构。不过没关系。您应该为访问者知道如何访问的每种类型的事物编写专门的访问操作。这允许访问者决定它真正想要访问多少,以及以什么顺序访问。
假设你有一棵树。一个访问者可能会进行前序遍历,一个可能会进行中序遍历,而另一个访问者可能只作用于叶节点。访问者类可以做所有这些事情,而不需要对树类进行任何更改。
访问者知道结构,但这并不一定意味着访问者执行的操作知道所有结构。您可以将访问者与命令结合起来。给访问者对象一个命令对象,访问者将在它访问的每个事物上调用该命令。
如果您想进行简单的操作并让集合为您提供要操作的每个项目,那么您希望集合为自己提供一个迭代器。在迭代器给你的每一件事上调用你的函数。
如果您想以各种顺序迭代树的节点,那么树将需要提供多个迭代器。如果要按树不支持的顺序处理节点,则需要修改树类。
是的。被访问的对象可以进行枚举(即调用所需的子对象)。这仍然被称为“访问者”模式(实际上,设计模式的第一个访问者示例就是这样做的)。我编造的示例片段:
public void accept(Visitor visitor) {
for (Node n : children) {
n.accept(visitor);
}
}
注意:为了探望孩子,我们不能说visitor.visit(n);
。这是因为 Java 不会动态选择方法(基于其参数的运行时类),而是静态选择方法(通过其参数的编译时类型)。
简而言之,我认为访问者模式与枚举的方式是正交的。它可以以任何一种方式完成,或者根本不枚举。
我认为访问者需要知道访问结构由哪些元素组成。想知道汽车由车轮和发动机组成。我认为没有必要知道它们是如何组合的。考虑以下示例。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 实现中实现它。所以如果我忘记做这件事——我会得到编译器错误。在这种情况下,枚举(如果有)是在访问的结构和访问者之外完成的。但我仍然认为它是访问者模式的一个有效案例。它碰巧实现起来有点困难,但使用起来更容易、更安全。
分层访问者模式解释了一种不同的方法,它添加了进入和离开关卡的事件。相关的讨论页面提供了在访问者或容器内进行迭代的参数。它包括一个使用外部迭代器的建议,如果你有一个常规的树并且需要以不同的方式迭代,这对我来说很有意义。
回顾我的oofRep 访问者,它有一系列不同级别的类要访问,并且在方法中进行了迭代,例如:
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";
}