为什么访问者负责枚举访问者模式中的子项?

发布于 2024-07-13 03:39:32 字数 136 浏览 8 评论 0原文

根据我发现的代码,访问者似乎需要了解所访问对象的结构并调用所需的子对象。 在某些情况下,即使访问的类被修改,访问者仍希望继续工作,这似乎有点笨拙。

我想真正的问题是:他们的枚举是由访问的代码而不是访问者代码完成的吗?

Based on the code I've found, it seems that the Visitor is required to known the structure of the visited objects and call on the needed children. This seems a bit clunky in some cases where the visitor would want to continue to work even if the visited classes are modified.

I guess the real question is: Is their a pattern where the enumeration is done by the visited code rather than the visitor code?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

唐婉 2024-07-20 03:39:32

访问者对象需要知道它访问的事物的结构。 不过没关系。 您应该为访问者知道如何访问的每种类型的事物编写专门的访问操作。 这使得访问者可以决定自己真正想要访问的程度以及访问顺序。

假设你有一棵树。 一个访问者可能会进行前序遍历,一个访问者可能会进行中序遍历,而另一位访问者可能只对叶节点进行操作。 访问者类可以完成所有这些事情,而不需要对树类进行任何更改。

访问者知道该结构,但这并不一定意味着访问者执行的操作知道所有结构。 您可以将访问者与命令组合起来。命令。 为访问者对象提供一个命令对象,访问者将在其访问的每个对象上调用该命令。

如果您想要进行简单的操作并让集合为您提供要操作的每个项目,那么您希望集合为自己提供一个迭代器。 对迭代器为您提供的每件事调用您的函数。

如果您想以不同的顺序迭代树的节点,那么树将需要提供多个迭代器。 如果您想按照树尚不支持的顺序处理节点,则需要修改树类。

The visitor object is required to know the structure of the things it visits. That's OK, though. You're supposed to write specialized visit operations for each type of thing the visitor knows how to visit. This allows the visitor to decide how much it really wants to visit, and in what order.

Suppose you have a tree. One visitor might do a pre-order traversal, one might do an in-order traversal, and yet another visitor might act only on leaf nodes. The visitor classes can do all these things without requiring any changes to the tree class.

The visitor knows the structure, but that doesn't necessarily mean the operation the visitor performs knows all the structure. You might combine a visitor with a command. Give the visitor object a command object, and the visitor will invoke the command on each thing it visits.

If you want to have a simple operation and let the collection give you each item to act on, then you want the collection to provide an iterator for itself. Call your function on each thing the iterator gives you.

If you want to iterate over the tree's nodes in various orders, then the tree will need to offer multiple iterators. If you want to process nodes in an order that the tree doesn't already support, you'll need to modify the tree class.

中性美 2024-07-20 03:39:32

是的。 被访问的对象可以进行枚举(即调用所需的子对象)。 这仍然称为“Visitor”模式(事实上,设计模式的第一个 Visitor 示例就是这样做的)。 我编造的示例片段:

public void accept(Visitor visitor) {
  for (Node n : children) {
    n.accept(visitor);
  }
}

注意:对于拜访孩子们,我们不能说 visitor.visit(n);。 这是因为 Java 不是动态选择方法(根据其参数的运行时类),而是静态选择方法(根据其参数的编译时类型)。

Yes. The visited objects can do the enumeration (i.e. call on the needed children). This is still called the "Visitor" pattern (in fact, Design Pattern's first sample of Visitor does it this way). My made-up example snippet:

public void accept(Visitor visitor) {
  for (Node n : children) {
    n.accept(visitor);
  }
}

Note: for visiting the children, we can't say visitor.visit(n);. This is because Java does not dynamically select the method (based on the runtime class of its arguments), but selects the method statically (by the compile-time type of its arguments).

雾里花 2024-07-20 03:39:32

简而言之,我认为访问者模式与枚举的完成方式正交。 可以用任何一种方式完成,也可以根本不进行枚举。

我认为访问者需要知道访问的结构由哪些元素组成。 想知道汽车由车轮和发动机组成。 我认为没有必要知道它们到底是如何组合的。 考虑以下示例。 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 实现中实现它。 所以如果我忘记做一些事情 - 我会得到编译器错误。
这种情况下的枚举(如果有的话)是在访问的结构和访问者之外完成的。 但我仍然认为这是访客模式的一个有效案例。
它实施起来有点困难,但使用起来更容易、更安全。

In short words, I think that Visitor pattern is orthogonal to the way enumeration is done. It can be done either way, or no enumeration at all.

I think that visitor is required to know what elements visited structure consists of. Like to know that car consists of Wheels and Engine. To know how exactly they are combined is not necessary, I think. Consider following example. Insider knows visited object structure and performs enumeration itself. Outsider does not know it and delegates enumeration to the visited object.

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);
    }
}

I had a project with wide usage of visitor pattern without any enumeration at all. We had base interface Field and many classes implementing it, like StringField, NumberField, etc. Very often we had to do different things based on the field type, for example render it in a different way, load from DB, export to xml, etc. We could define methods in Field interface, but that would make it coupled with every single feature of the project - poor field has to know about exporting, importing, rendering to html and rtf, etc. We also could use instanceof but set of possible classes implementing Field interface was changed over time and it was possible to add new field type and forget to add

else if (field instanceof NewlyAddedFieldType) {...}

somewhere. So we decided to use visitor pattern, and it was like

Visitor v = new XMLExportVisitor(outputStream);
field.accept(v);

As any Field implementation is required to have method

void accept(FieldVisitor visitor)

then if I add new implementation of Field interface, I have to implement it somehow. Normally it is

visitor.visit(this);

where this is a newly added class. This forces me to add

void visit(NewlyAddedClass visited);

to FieldVisitor interface, which makes me implement it in every FieldVisitor implementation that we already have. So if I forget to do something of this - I'll get compiler error.
Enumeration in this case, if any, was done outside of visited structure and visitor. But I still think about it as a valid case of visitor pattern.
It happend to be a little bit harder to implement, but easier and safer to use.

嗼ふ静 2024-07-20 03:39:32

分层访问者模式解释了一种不同的方法,它添加了进入和离开级别的事件。 关联的讨论页面提供访问者或容器内迭代的参数。 它包括使用外部迭代器的建议,如果您有一个常规树并且需要以不同方式进行迭代,这对我来说很有意义。

回顾我的 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";
}

The Hierarchical Visitor Pattern explains a different approach where it adds events for entering and leaving levels. The associated discussion page presents arguments for iteration being within the visitor or the container. It includes a suggestion of using an external iterator which makes sense to me if you have a regular tree and need to iterate differently.

Looking back to my oofRep visitor it had a series of levels of different classes to visit and had the iteration within methods like:

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();
}

with an override

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";
}
走野 2024-07-20 03:39:32

看看这篇文章中的解释。

来自 Wiki

在面向对象编程中
软件工程,访客
设计模式是一种分离的方式
来自对象结构的算法
它的运作依据。 一个实用的
这种分离的结果是
添加新操作的能力
现有的对象结构没有
修改这些结构。 因此,
使用访客模式有帮助
符合开放/封闭
原理。

Have a look at the explanation in this article.

From Wiki:

In object-oriented programming and
software engineering, the visitor
design pattern is a way of separating
an algorithm from an object structure
upon which it operates. A practical
result of this separation is the
ability to add new operations to
existing object structures without
modifying those structures. Thus,
using the visitor pattern helps
conformance with the open/closed
principle.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文