Patrón de diseño de visitantes en Java

1. Información general

En este tutorial, presentaremos uno de los patrones de diseño de comportamiento de GoF: el visitante.

Primero, explicaremos su propósito y el problema que intenta resolver.

A continuación, veremos el diagrama UML de Visitor y la implementación del ejemplo práctico.

2. Patrón de diseño de visitantes

El propósito de un patrón de visitante es definir una nueva operación sin introducir modificaciones en una estructura de objeto existente.

Imagina que tenemos un compuestoobjeto que consta de componentes. La estructura del objeto es fija: no podemos cambiarla o no planeamos agregar nuevos tipos de elementos a la estructura.

Ahora, ¿cómo podríamos agregar nueva funcionalidad a nuestro código sin modificar las clases existentes?

El patrón de diseño del visitante podría ser una respuesta. En pocas palabras, lo que tendremos que hacer es agregar una función que acepte la clase de visitante a cada elemento de la estructura.

De esa manera, nuestros componentes permitirán que la implementación del visitante los “visite” y realice cualquier acción requerida en ese elemento.

En otras palabras, extraeremos el algoritmo que se aplicará a la estructura del objeto de las clases.

En consecuencia, haremos un buen uso del principio Abierto / Cerrado ya que no modificaremos el código, pero aún podremos extender la funcionalidad proporcionando una nueva implementación de Visitor .

3. Diagrama UML

En el diagrama UML anterior, tenemos dos jerarquías de implementación, visitantes especializados y elementos concretos.

En primer lugar, el cliente usa una implementación de Visitor y la aplica a la estructura del objeto. El objeto compuesto itera sobre sus componentes y aplica al visitante a cada uno de ellos.

Ahora, es especialmente relevante que los elementos de concreto (ConcreteElementA y ConcreteElementB) están aceptando un Visitante, simplemente permitiéndole visitarlos .

Por último, este método es el mismo para todos los elementos de la estructura, realiza un envío doble pasándose (a través de la palabra clave this ) al método de visita del visitante.

4. Implementación

Nuestro ejemplo será un objeto de documento personalizado que consta de elementos concretos JSON y XML; los elementos tienen una superclase abstracta común, el Elemento.

La clase de documento :

public class Document extends Element { List elements = new ArrayList(); // ... @Override public void accept(Visitor v) { for (Element e : this.elements) { e.accept(v); } } }

La clase Element tiene un método abstracto que acepta la interfaz de visitante :

public abstract void accept(Visitor v);

Por lo tanto, al crear el nuevo elemento, asígnele el nombre JsonElement , tendremos que proporcionar la implementación de este método.

Sin embargo, debido a la naturaleza del patrón de visitante, la implementación será la misma, por lo que en la mayoría de los casos, sería necesario que copiemos y peguemos el código repetitivo de otro elemento ya existente:

public class JsonElement extends Element { // ... public void accept(Visitor v) { v.visit(this); } }

Dado que nuestros elementos permiten que los visite cualquier visitante, digamos que queremos procesar nuestros elementos de Documento , pero cada uno de ellos de forma diferente, dependiendo de su tipo de clase.

Por lo tanto, nuestro visitante tendrá un método separado para el tipo dado:

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

Aquí, nuestro visitante concreto implementa dos métodos, correspondientemente uno por cada tipo de Elemento .

Esto nos da acceso al objeto particular de la estructura sobre el que podemos realizar las acciones necesarias.

5. Prueba

Para fines de prueba, echemos un vistazo a la clase 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); } // ... }

Primero, creamos un visitante de elementos , contiene el algoritmo que aplicaremos a nuestros elementos.

A continuación, configuramos nuestro Documento con los componentes adecuados y aplicamos el visitante que será aceptado por cada elemento de una estructura de objeto.

La salida sería así:

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

Muestra que el visitante ha visitado cada elemento de nuestra estructura, dependiendo del tipo de Elemento , envió el procesamiento al método apropiado y pudo recuperar los datos de cada objeto subyacente.

6. Desventajas

Como cada patrón de diseño, incluso el visitante tiene sus desventajas, en particular, su uso hace que sea más difícil mantener el código si necesitamos agregar nuevos elementos a la estructura del objeto.

Por ejemplo, si agregamos un nuevo YamlElement, entonces necesitamos actualizar todos los visitantes existentes con el nuevo método deseado para procesar este elemento. Después de esto, si tenemos diez o más visitantes concretos, podría ser complicado actualizarlos todos.

Aparte de esto, cuando se utiliza este patrón, la lógica empresarial relacionada con un objeto en particular se extiende a todas las implementaciones de visitantes.

7. Conclusión

El patrón de visitante es excelente para separar el algoritmo de las clases en las que opera. Además de eso, facilita la adición de nuevas operaciones, simplemente proporcionando una nueva implementación del Visitor.

Además, no dependemos de las interfaces de los componentes, y si son diferentes, está bien, ya que tenemos un algoritmo separado para procesar por elemento concreto.

Además, el visitante puede eventualmente agregar datos en función del elemento que atraviesa.

Para ver una versión más especializada del patrón de diseño de visitante, consulte el patrón de visitante en Java NIO: el uso del patrón en el JDK.

Como de costumbre, el código completo está disponible en el proyecto Github.