关于访客模式的问题(Java 示例)

发布于 2024-11-14 05:33:57 字数 4054 浏览 3 评论 0原文

我只是想了解使用访问者模式的主要好处。

这是一个 Java 实现示例,

///////////////////////////////////
// Interfaces
interface MamalVisitor {
    void visit(Mammal mammal);
}
interface MammalVisitable {
    public void accept(MamalVisitor visitor);
}
interface Mammal extends MammalVisitable {
    public int getLegsNumber();
}
///////////////////////////////////


///////////////////////////////////
// Model
class Human implements Mammal {
    @Override
    public void accept(MamalVisitor visitor) {  visitor.visit(this);  }
    @Override
    public int getLegsNumber() { return 2; }
}
//PIRATE HAS A WOOD LEG
class Pirate extends Human { 
    @Override
    public int getLegsNumber() { return 1; }
    public int getWoodLegNumber() { return 1; }
}
class Dog implements Mammal {
    @Override
    public void accept(MamalVisitor visitor) {  visitor.visit(this);  }
    @Override
    public int getLegsNumber() { return 4; }
}
///////////////////////////////////


///////////////////////////////////
class LegCounterVisitor implements MamalVisitor {
    private int legNumber = 0;
    @Override
    public void visit(Mammal mammal) {   legNumber += mammal.getLegsNumber();   }
    public int getLegNumber() { return legNumber; }
}
class WoodLegCounterVisitor implements MamalVisitor {
    private int woodLegNumber = 0;
    @Override
    public void visit(Mammal mammal) {   
        // perhaps bad but i'm lazy
        if ( mammal instanceof Pirate ) {
            woodLegNumber += ((Pirate) mammal).getWoodLegNumber();
        }
    }
    public int getWoodLegNumber() { return woodLegNumber; }
}
///////////////////////////////////



///////////////////////////////////
public class Main {
    public static void main(String[] args) {
        // Create a list with 9 mammal legs and 3 pirate woodlegs
        List<Mammal> mammalList = Arrays.asList(
                new Pirate(),
                new Dog(),
                new Human(),
                new Pirate(),
                new Pirate()
        );

        ///////////////////////////////////
        // The visitor method
        LegCounterVisitor legCounterVisitor = new LegCounterVisitor();
        WoodLegCounterVisitor woodLegCounterVisitor = new WoodLegCounterVisitor();
        for ( Mammal mammal : mammalList ) {
            mammal.accept(legCounterVisitor);
            mammal.accept(woodLegCounterVisitor);
            // why not also using:
            // legCounterVisitor.visit(mammal);
            // woodLegCounterVisitor.visit(mammal);
        }
        System.out.println("Number of legs:" + legCounterVisitor.getLegNumber());
        System.out.println("Number of wood legs:" + woodLegCounterVisitor.getWoodLegNumber());

        ///////////////////////////////////
        // The standart method
        int legNumber = 0;
        int woodLegNumber = 0;
        for ( Mammal mammal : mammalList ) {
            legNumber += mammal.getLegsNumber();
            // perhaps bad but i'm lazy
            if ( mammal instanceof Pirate ) {
                woodLegNumber += ((Pirate) mammal).getWoodLegNumber();
            }
        }
        System.out.println("Number of legs:" + legNumber);
        System.out.println("Number of wood legs:" + woodLegNumber);
    }
}
///////////////////////////////////

我只是想知道在这种情况下使用这种模式的主要优点是什么。我们还可以迭代集合并获得几乎相同的东西,只不过我们不必处理新接口并向模型添加样板代码......

使用 Apache Commons 或函数式语言,经典方法似乎可以做到一些映射/减少操作(映射到腿号并通过加法减少),这很容易...

我也想知道为什么我们使用

        mammal.accept(legCounterVisitor);
        mammal.accept(woodLegCounterVisitor);

而不是

        legCounterVisitor.visit(mammal);
        woodLegCounterVisitor.visit(mammal);

第二个选项似乎删除了模型部分上的accept(...)方法。

在我发现的许多示例中,它们似乎没有使用模型对象的通用接口。我添加它是因为这样我只需要添加一个访问(哺乳动物)方法,而不是为每个哺乳动物实现一个方法。 让我的所有对象都实现 Mammal 好吗? (我想有时无论如何都是不可能的)。仍然是这样的访客模式吗?

所以我的问题是: - 您认为我的例子中使用访客有什么优势吗? - 如果没有,您能为访问者提供一些具体的用例吗? - 访问者在函数式编程语言中有用吗

我发现与此模式相关的唯一示例是漂亮打印机的情况,其中您将访问者状态中的偏移量保存在访问不同节点期间使用的偏移量(用于显示 XML 树)示例)

I'm just trying to understand the main benefits of using the Visitor pattern.

Here's a sample Java implementation

///////////////////////////////////
// Interfaces
interface MamalVisitor {
    void visit(Mammal mammal);
}
interface MammalVisitable {
    public void accept(MamalVisitor visitor);
}
interface Mammal extends MammalVisitable {
    public int getLegsNumber();
}
///////////////////////////////////


///////////////////////////////////
// Model
class Human implements Mammal {
    @Override
    public void accept(MamalVisitor visitor) {  visitor.visit(this);  }
    @Override
    public int getLegsNumber() { return 2; }
}
//PIRATE HAS A WOOD LEG
class Pirate extends Human { 
    @Override
    public int getLegsNumber() { return 1; }
    public int getWoodLegNumber() { return 1; }
}
class Dog implements Mammal {
    @Override
    public void accept(MamalVisitor visitor) {  visitor.visit(this);  }
    @Override
    public int getLegsNumber() { return 4; }
}
///////////////////////////////////


///////////////////////////////////
class LegCounterVisitor implements MamalVisitor {
    private int legNumber = 0;
    @Override
    public void visit(Mammal mammal) {   legNumber += mammal.getLegsNumber();   }
    public int getLegNumber() { return legNumber; }
}
class WoodLegCounterVisitor implements MamalVisitor {
    private int woodLegNumber = 0;
    @Override
    public void visit(Mammal mammal) {   
        // perhaps bad but i'm lazy
        if ( mammal instanceof Pirate ) {
            woodLegNumber += ((Pirate) mammal).getWoodLegNumber();
        }
    }
    public int getWoodLegNumber() { return woodLegNumber; }
}
///////////////////////////////////



///////////////////////////////////
public class Main {
    public static void main(String[] args) {
        // Create a list with 9 mammal legs and 3 pirate woodlegs
        List<Mammal> mammalList = Arrays.asList(
                new Pirate(),
                new Dog(),
                new Human(),
                new Pirate(),
                new Pirate()
        );

        ///////////////////////////////////
        // The visitor method
        LegCounterVisitor legCounterVisitor = new LegCounterVisitor();
        WoodLegCounterVisitor woodLegCounterVisitor = new WoodLegCounterVisitor();
        for ( Mammal mammal : mammalList ) {
            mammal.accept(legCounterVisitor);
            mammal.accept(woodLegCounterVisitor);
            // why not also using:
            // legCounterVisitor.visit(mammal);
            // woodLegCounterVisitor.visit(mammal);
        }
        System.out.println("Number of legs:" + legCounterVisitor.getLegNumber());
        System.out.println("Number of wood legs:" + woodLegCounterVisitor.getWoodLegNumber());

        ///////////////////////////////////
        // The standart method
        int legNumber = 0;
        int woodLegNumber = 0;
        for ( Mammal mammal : mammalList ) {
            legNumber += mammal.getLegsNumber();
            // perhaps bad but i'm lazy
            if ( mammal instanceof Pirate ) {
                woodLegNumber += ((Pirate) mammal).getWoodLegNumber();
            }
        }
        System.out.println("Number of legs:" + legNumber);
        System.out.println("Number of wood legs:" + woodLegNumber);
    }
}
///////////////////////////////////

I just wonder what is the main advantage for this case to use such a pattern. We can also iterate over the collection and get almost the same thing, except we don't have to handle a new interface and add boilerplate code to the model...

With Apache Commons, or a functional language, the classic way seems to do some map/reduce operation (map to the leg numbers and reduce with addition) and it's quite easy...

I also wonder why we use

        mammal.accept(legCounterVisitor);
        mammal.accept(woodLegCounterVisitor);

and not

        legCounterVisitor.visit(mammal);
        woodLegCounterVisitor.visit(mammal);

The 2nd option seems to remove the accept(...) method on the model part.

In many samples i've found, it seems that they don't use a common interface for model objects. I added it because like that i just have to add one visit(Mammal) method, instead of implementing one for each Mammal.
Is it good to make all my objects implement Mammal? (i guess sometimes it's just not possible anyway). Is it still a Visitor pattern like that?

So my questions are:
- do you see any advantage in my exemple for using visitors?
- if not, can you provide some concrete usecases for visitors?
- are visitors useful in functional programming languages

The only exemple that i found relevant for this pattern is the case of a pretty printer, where you keep in the visitor's state the offset to use during the visit of different nodes (for displaying an XML tree for exemple)

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

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

发布评论

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

评论(4

红ご颜醉 2024-11-21 05:33:57

访问者模式只是双重调度

我不确定我是否同意您对访客的实施。我会实现这样的东西:

interface MammalVisitor {
    void visit(Pirate pirate);
    void visit(Human human);
    void visit(Dog dog);
}

// Basic visitor provides no-op behaviour for everything.
abstract class MammalAdapter implements MammalVisitor {
    void visit(Pirate pirate) {};
    void visit(Human human) {};
    void visit(Dog dog) {};
}

然后实现会变得更清晰:

// We only want to provide specific behaviour for pirates
class WoodLegCounterVisitor extends MammalAdaptor {
    private int woodLegNumber = 0;
    @Override
    public void visit(Pirate pirate) {   
        woodLegNumber += pirate.getWoodLegNumber();
    }

    public int getWoodLegNumber() { return woodLegNumber; }
}

在回答您的实际问题时,使用访问者的主要优点是避免需要进行“instanceof”检查。它使您能够将处理层次结构的逻辑分离到单独的类中。它还使您能够在不更改原始类的情况下添加新行为。

The visitor pattern is just double dispatch.

I'm not sure I agree with your implementation of a visitor. I'd implement something like this:

interface MammalVisitor {
    void visit(Pirate pirate);
    void visit(Human human);
    void visit(Dog dog);
}

// Basic visitor provides no-op behaviour for everything.
abstract class MammalAdapter implements MammalVisitor {
    void visit(Pirate pirate) {};
    void visit(Human human) {};
    void visit(Dog dog) {};
}

And then the implementation would become cleaner:

// We only want to provide specific behaviour for pirates
class WoodLegCounterVisitor extends MammalAdaptor {
    private int woodLegNumber = 0;
    @Override
    public void visit(Pirate pirate) {   
        woodLegNumber += pirate.getWoodLegNumber();
    }

    public int getWoodLegNumber() { return woodLegNumber; }
}

In answer to your actual question, the main advantage of using the visitor is avoiding the need to do the "instanceof" checks. It gives you the ability to separate out the logic for processing a hierarchy into a separate class. It also gives you the ability to add new behaviour without changing the original classes.

寄人书 2024-11-21 05:33:57

访客模式是一个奇特的 switch case / 模式匹配系统,可促进 图遍历

由于典型的函数式语言提供模式匹配和遍历图形的有效方法,因此兴趣更加有限。

即使在 JAVA 中,使用 instanceof 或使用 enum,访问者也更像是一种奇特的执行方式,而不是通用解决方案,因为许多算法不能很好地适应它。

Visitor pattern is a fancy switch case / pattern matching system to facilitate graph traversal.

As typical functional languages offer pattern matching and efficient ways to traverse graphs, interest is much more limited.

Even in JAVA, with instanceof or using enum, a visitor is more of a fancy way to perform things than a generic solution as many algorithms will not fit well into it.

青柠芒果 2024-11-21 05:33:57

访问者模式的目的是分离对象结构(在您的情况下,哺乳动物)来自算法(在您的例子中,是计数器腿计数器算法)。

整个想法是你的对象(主要是java,JavaBeans)根本不改变它的结构,而只是一个新的 虚函数的引入是为了引入新的算法。

Jeff Foster 的实现不同,我们可以使用泛型来简化代码。这为您的访客带来了特殊性,例如:

public interface MammalVisitor<T extends Mammal> {

    public void visit(T mammal);
}

public class LegCounterVisitor implements MamalVisitor<Human> {
    private int legNumber = 0;
    @Override
    public void visit(Human mammal) {   legNumber += mammal.getLegsNumber();   }
    public int getLegNumber() { return legNumber; }
}

public class WoodLegCounterVisitor implements MamalVisitor<Pirate> {
    private int legNumber = 0;
    @Override
    public void visit(Pirate mammal) {legNumber += mammal.getWoodLegNumber();   }
    public int getLegNumber() { return legNumber; }
}

The purpose of the Visitor Pattern is to separate the object structure (in your case, Mammal) from the algorithm (in your case, the counter Leg counter algorithm).

The whole idea is that your object (mostly in java, JavaBeans) doesn't change its structure at all, and only a new virtual function is introduced to introduce a new algorithm.

Unlike Jeff Foster's implementation, One can use Generics to make code easier. This brings specificity to your visitor, e.g.:

public interface MammalVisitor<T extends Mammal> {

    public void visit(T mammal);
}

public class LegCounterVisitor implements MamalVisitor<Human> {
    private int legNumber = 0;
    @Override
    public void visit(Human mammal) {   legNumber += mammal.getLegsNumber();   }
    public int getLegNumber() { return legNumber; }
}

public class WoodLegCounterVisitor implements MamalVisitor<Pirate> {
    private int legNumber = 0;
    @Override
    public void visit(Pirate mammal) {legNumber += mammal.getWoodLegNumber();   }
    public int getLegNumber() { return legNumber; }
}
惟欲睡 2024-11-21 05:33:57

似乎上面的所有答案都在数据结构/算法层面提供了详细的解释,但是仍然缺乏高层/概念性的理解。而缺乏概念上的理解可能会导致误解,认为访问者模式只不过是一种花哨的风格。

恕我直言,让我们考虑一下域实体之间的所有交互都可以简化为一对一的交互。访问者模式适用于这样的情况:一个实体保持稳定的类型,而另一个实体具有多个专业化并且正在动态扩展。在这种情况下,稳定实体可以建模为元素(或可访问),动态实体可以建模为访问者。

有关双重调度的更详细说明,请考虑以下假设:Class AClass B 之间存在交互,其中 m>n 个专业化,分别。我们必须为每个组合实现所有m*n 方法。我们假设 m n,因此我们将 A 类 表示为 Element,将 B 类 表示为 Visitor

  • 首先,我们需要设计一条分支为 m*n 条路径的路线。 Element.accept 方法进行第一次选择,而 Visitor.visit 方法进行第二次选择。
  • 很明显,Element 应该更稳定(意味着数量更少),就像它位于决策树中的更高级别一样。
  • 在这个三层树中,顶层代表起始点,第二层对应每个 Element 专门化,第三层代表每个 下的多个 Visitor元素 实现。树的叶子代表相应的ElementVisitor 的方法。
  • 要向 Element 添加新的专门化,每个 Visitor 必须在不同的代码文件中实现新方法,从而导致低内聚性。相反,添加一个新的Visitor只需要在单个文件中实现所有方法,而其他文件不变,这是高度解耦的。

我相信,在这些上下文之外,很难判断任何交付是否合理的访问者模式,因为任何交互都可以实现为 A.acceptB.visit

代码设计的一个关键区别是访问者始终知道可访问对象的确切类型,但反之则不然。

希望这有帮助。

It seems all the answers above provided a detailed explanation at the data structure/algorithm level, however, a high-level/conceptual understanding is still lacking. And the lacking of conceptual understanding may lead to misunderstanding that Visitor Pattern is nothing more than a fancy style.

IMHO, let's consider that all interactions among your domain entities could be reduced to one-to-one interactions. The Visitor Pattern is suitable for such a situation where one entity maintains a stable type(s), while another entity has multiple specializations and is dynamically expanding. Under these circumstances, the stable entity can be modeled as the Element (or Visitable), and the dynamic one as the Visitor.

For a more detailed explanation regarding Double Dispatch, consider this assumption: there is an interaction between Class A and Class B, with m and n specializations, respectively. We must implement all m*n methods for each combination. Let's assume m < n, so we'll denote Class A as Element and Class B as Visitor.

  • To begin with, we need to design a route that branches into m*n paths. The Element.accept method makes the first selection, while the Visitor.visit method makes the second.
  • It's clear that the Element should be more stable (meaning fewer in number), like it sits at a higher level in the decision tree.
  • In this 3-level tree, the top level represents the starting point, the second level corresponds to each Element specialization, and the third level represents multiple Visitors under each Element implementation. The leaves of the tree symbolize the methods for the corresponding Element and Visitor.
  • To add a new specialization to the Element, every Visitor must implement a new method across different code files, resulting in low cohesion. Conversely, adding a new Visitor only requires implementing all methods in a single file, leaving other files untouched, which is highly decoupled.

I believe that out of these context, any delivery is hard to be judged as whether reasonable Visitor Pattern since any interaction could be implemented as A.accept and B.visit.

A key distinction in the design of the code is that the visitor always knows the exact type of the visitable, but not the other way around.

Hope this helps。

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