Visitor Design Pattern in Java – Java中的访问者设计模式

最后修改: 2018年 6月 4日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll introduce one of the behavioral GoF design patterns – the Visitor.

在本教程中,我们将介绍GoF设计模式中的一个行为模式–访问者。

First, we’ll explain its purpose and the problem it tries to solve.

首先,我们将解释其目的和它试图解决的问题。

Next, we’ll have a look at Visitor’s UML diagram and implementation of the practical example.

接下来,我们来看看Visitor的UML图和实际例子的实现。

2. Visitor Design Pattern

2.访客设计模式

The purpose of a Visitor pattern is to define a new operation without introducing the modifications to an existing object structure.

Visitor模式的目的是定义一个新的操作,而不对现有的对象结构引入修改。

Imagine that we have a composite object which consists of components. The object’s structure is fixed – we either can’t change it, or we don’t plan to add new types of elements to the structure.

想象一下,我们有一个composite对象,它由组件组成。该对象的结构是固定的 – 我们要么不能改变它,要么我们不打算向该结构添加新的元素类型。

Now, how could we add new functionality to our code without modification of existing classes?

现在,我们怎样才能在不修改现有类的情况下为我们的代码添加新的功能?

The Visitor design pattern might be an answer. Simply put, we’ll have to do is to add a function which accepts the visitor class to each element of the structure.

访客设计模式可能是一个答案。简单地说,我们要做的是为结构中的每个元素添加一个接受访问者类的函数。

That way our components will allow the visitor implementation to “visit” them and perform any required action on that element.

这样,我们的组件将允许访问者实现 “访问 “它们,并对该元素执行任何必要的操作。

In other words, we’ll extract the algorithm which will be applied to the object structure from the classes.

换句话说,我们将从类中提取将被应用于对象结构的算法。

Consequently, we’ll make good use of the Open/Closed principle as we won’t modify the code, but we’ll still be able to extend the functionality by providing a new Visitor implementation.

因此,我们将很好地利用开放/封闭原则,因为我们不会修改代码,但是我们仍然能够通过提供一个新的Visitor实现来扩展功能。

3. UML Diagram

3.UML图示

Visitor-UML

On the UML diagram above, we have two implementation hierarchies, specialized visitors, and concrete elements.

在上面的UML图中,我们有两个实现层次,专门的访问者,和具体的元素。

First of all, the client uses a Visitor implementation and applies it to the object structure. The composite object iterates over its components and applies the visitor to each of them.

首先,客户端使用一个Visitor实现并将其应用于对象结构。复合对象对其组件进行迭代,并将访问者应用到每个组件中。

Now, especially relevant is that concrete elements (ConcreteElementA and ConcreteElementB) are accepting a Visitor, simply allowing it to visit them.

现在,特别相关的是,具体元素(ConcreteElementAConcreteElementB)正在接受一个Visitor,简单地允许它访问它们。

Lastly, this method is the same for all elements in the structure, it performs double dispatch with passing itself (via the this keyword) to the visitor’s visit method.

最后,这个方法对结构中的所有元素都是一样的,它执行双重调度,将自己(通过this关键字)传递给访问者的访问方法。

4. Implementation

4.实施

Our example will be custom Document object that consists of JSON and XML concrete elements; the elements have a common abstract superclass, the Element.

我们的例子将是由JSON和XML具体元素组成的自定义Document对象;这些元素有一个共同的抽象超类,Element.

The Document class:

Document类。

public class Document extends Element {

    List<Element> elements = new ArrayList<>();

    // ...

    @Override
    public void accept(Visitor v) {
        for (Element e : this.elements) {
            e.accept(v);
        }
    }
}

The Element class has an abstract method which accepts the Visitor interface:

Element类有一个抽象方法,接受Visitor接口。

public abstract void accept(Visitor v);

Therefore, when creating the new element, name it the JsonElement, we’ll have to provide the implementation this method.

因此,在创建新元素时,将其命名为JsonElement,我们将不得不提供这个方法的实现。

However, due to nature of the Visitor pattern, the implementation will be the same, so in most cases, it would require us to copy-paste the boilerplate code from other, already existing element:

然而,由于Visitor模式的性质,其实现将是相同的,所以在大多数情况下,这将要求我们从其他已经存在的元素中复制粘贴模板代码。

public class JsonElement extends Element {

    // ...

    public void accept(Visitor v) {
        v.visit(this);
    }
}

Since our elements allow visiting them by any visitor, let’s say that we want to process our Document elements, but each of them in a different way, depending on its class type.

由于我们的元素允许任何访问者访问它们,让我们说我们想处理我们的Document 元素,但每个元素以不同的方式处理,这取决于它的类别类型。

Therefore, our visitor will have a separate method for the given type:

因此,我们的访问者将为给定的类型有一个单独的方法。

public class ElementVisitor implements Visitor {

    @Override
    public void visit(XmlElement xe) {
        System.out.println(
          "processing an XML element with uuid: " + xe.uuid);
    }

    @Override
    public void visit(JsonElement je) {
        System.out.println(
          "processing a JSON element with uuid: " + je.uuid);
    }
}

Here, our concrete visitor implements two methods, correspondingly one per each type of the Element.

在这里,我们的具体访问者实现了两个方法,相应地,每种类型的Element都有一个。

This gives us access to the particular object of the structure on which we can perform necessary actions.

这使我们能够访问结构中的特定对象,我们可以对其进行必要的操作。

5. Testing

5.测试

For testing purpose, let’s have a look at VisitorDemoclass:

为了测试,让我们看一下VisitorDemo类。

public class VisitorDemo {

    public static void main(String[] args) {

        Visitor v = new ElementVisitor();

        Document d = new Document(generateUuid());
        d.elements.add(new JsonElement(generateUuid()));
        d.elements.add(new JsonElement(generateUuid()));
        d.elements.add(new XmlElement(generateUuid()));

        d.accept(v);
    }

    // ...
}

First, we create an ElementVisitor, it holds the algorithm we will apply to our elements.

首先,我们创建一个ElementVisitor,它持有我们将应用于元素的算法。

Next, we set up our Document with proper components and apply the visitor which will be accepted by every element of an object structure.

接下来,我们用适当的组件设置我们的Document,并应用访问者,这将被对象结构的每个元素所接受。

The output would be like this:

输出结果将是这样的。

processing a JSON element with uuid: fdbc75d0-5067-49df-9567-239f38f01b04
processing a JSON element with uuid: 81e6c856-ddaf-43d5-aec5-8ef977d3745e
processing an XML element with uuid: 091bfcb8-2c68-491a-9308-4ada2687e203

It shows that visitor has visited each element of our structure, depending on the Element type, it dispatched the processing to appropriate method and could retrieve the data from every underlying object.

这表明访问者已经访问了我们结构中的每个元素,根据Element 类型,它将处理过程分派给适当的方法,并可以从每个底层对象中检索数据。

6. Downsides

6.劣势

As each design pattern, even the Visitor has its downsides, particularly, its usage makes it more difficult to maintain the code if we need to add new elements to the object’s structure.

作为每一种设计模式,即使是Visitor也有它的缺点,特别是,如果我们需要在对象的结构中添加新的元素,它的使用使代码的维护更加困难。

For example, if we add new YamlElement, then we need to update all existing visitors with the new method desired for processing this element. Following this further, if we have ten or more concrete visitors, that might be cumbersome to update all of them.

例如,如果我们添加了新的YamlElement,,那么我们需要用处理这个元素所需的新方法来更新所有现有的访问者。继而,如果我们有十个或更多的具体访问者,那更新所有的访问者可能会很麻烦。

Other than this, when using this pattern, the business logic related to one particular object gets spread over all visitor implementations.

除此之外,当使用这种模式时,与一个特定对象相关的业务逻辑被分散到所有的访问者实现中。

7. Conclusion

7.结语

The Visitor pattern is great to separate the algorithm from the classes on which it operates. Besides that, it makes adding new operation more easily, just by providing a new implementation of the Visitor.

访客模式很好地将算法与它所操作的类分开。除此之外,它使得添加新的操作更加容易,只需提供一个新的Visitor实现。

Furthermore, we don’t depend on components interfaces, and if they are different, that’s fine, since we have a separate algorithm for processing per concrete element.

此外,我们不依赖于组件的接口,如果它们不同,那也没关系,因为我们对每个具体的元素都有单独的处理算法。

Moreover, the Visitor can eventually aggregate data based on the element it traverses.

此外,访问者最终可以根据它所穿越的元素来汇总数据。

To see a more specialized version of the Visitor design pattern, check out visitor pattern in Java NIO – the usage of the pattern in the JDK.

要查看 Visitor 设计模式的更专业版本,请查看Java NIO 中的visitor 模式 – 该模式在 JDK 中的用法。

As usual, the complete code is available on the Github project.

像往常一样,完整的代码可以在Github项目上获得。