Según el libro de GoF este patrón permite añadir funcionalidades a una clase sin tener que modificarla, siendo usado para manejar algoritmos, relaciones y responsabilidades entre objetos.
Así pues, nos resultará útil cuando necesitemos realizar operaciones distintas y no relacionadas sobre una estructura de objetos, aunque si lo utilizamos y luego tenemos que modificar alguna de las clases implicadas, hemos de tener en cuenta que se produce cierto nivel de acoplamiento entre ellas.
Veamos un ejemplo en el que simulando un juego equipamos con un arma a personajes de tipo Guerrero y con otra diferente a los que son de tipo Mago.
Main.java (hace las veces de Cliente y de ObjetoEstructura según el diagrama anterior):
package Visitor01;
import java.util.ArrayList;
import java.util.List;
public class Main
{
public static void main(String[] args)
{
// Crear los elementos
Guerrero g1 = new Guerrero();
Guerrero g2 = new Guerrero();
Mago m1 = new Mago();
Mago m2 = new Mago();
// Crear una lista para guardar los elementos
List<IPersonaje> personajes = new ArrayList<IPersonaje>();
// Agregar los elementos a una lista
personajes.add(g1);
personajes.add(g2);
personajes.add(m1);
personajes.add(m2);
// Creamos el Visitor y le pasamos la lista
IVisitor visitorArma = new EquiparArma();
visitorArma.visit( personajes );
// Comprobar el resultado:
System.out.println( "Arma del guerrero g1: " + g1.getArma() );
System.out.println( "Arma del guerrero g2: " + g2.getArma() );
System.out.println( "Arma del mago m1: " + m1.getArma() );
System.out.println( "Arma del mago m1: " + m2.getArma() );
}
}
IVisitor.java:
package Visitor01;
import java.util.List;
public interface IVisitor
{
public void visit( Mago m );
public void visit( Guerrero g );
public void visit( List<IPersonaje> elementList );
}
EquiparArma.java (un VisitorConcreto según el diagrama anterior):
package Visitor01;
import java.util.List;
public class EquiparArma implements IVisitor
{
@Override
public void visit( Mago m )
{
m.setArma("DAGA");
}
// ------------------------------
@Override
public void visit( Guerrero g )
{
g.setArma("ESPADA");
}
// ------------------------------
@Override
public void visit( List<IPersonaje> personajes )
{
for( IPersonaje p : personajes )
{
p.accept(this);
}
}
}
IPersonaje.java (un IElemento según el diagrama anterior):
package Visitor01;
public interface IPersonaje
{
public void accept( IVisitor visitor );
}
Guerrero.java (un ElementoConcreto según el diagrama anterior):
package Visitor01;
public class Guerrero implements IPersonaje
{
private String arma = "";
// ------------------------------
public Guerrero() {
}
// ------------------------------
public String getArma()
{
return this.arma;
}
// ------------------------------
public void setArma(String arma)
{
this.arma = arma;
}
// ------------------------------
@Override
public void accept( IVisitor visitor )
{
visitor.visit(this);
}
}
Mago.java (un ElementoConcreto según el diagrama anterior):
package Visitor01;
public class Mago implements IPersonaje
{
private String arma = "";
// ------------------------------
public Mago() {
}
// ------------------------------
public String getArma()
{
return this.arma;
}
// ------------------------------
public void setArma(String arma)
{
this.arma = arma;
}
// ------------------------------
@Override
public void accept( IVisitor visitor )
{
visitor.visit(this);
}
}
Al ejecutarlo obtendríamos como resultado:
EXPLICACIÓN:
- Al inicio del programa creamos una lista y la rellenamos con objetos de tipo IPersonaje.
- A continuación pasamos dicha lista al método visit() del objeto de tipo IVisitor, en el cual la recorreremos llamando al método accept() de cada objeto de tipo IPersonaje encontrado.
- Observa que el método aceptar() en los objetos de tipo IPersonaje no hace otra cosa que redirigir hacia el método visit() de los objetos de tipo IVisitor, en el cual se realizan las operaciones necesarias sobre el elemento del que se trate en cada caso.
Veamos a continuación otro ejemplo algo más completo, en el que creamos un nueva clase de tipo IVisitor llamada EquiparConjuro que se encargará de conceder conjuros únicamente a los elementos de tipo Mago.
Main.java (hace las veces de Cliente y de ObjetoEstructura según el diagrama anterior):
package Visitor02;
import java.util.ArrayList;
import java.util.List;
public class Main
{
public static void main(String[] args)
{
// Crear los guerreros
Guerrero g1 = new Guerrero();
Guerrero g2 = new Guerrero();
// Crear los magos
Mago m1 = new Mago();
Mago m2 = new Mago();
m1.setNivelMagia(3);
m2.setNivelMagia(7);
// Crear una lista para guardar los elementos
List<IPersonaje> personajes = new ArrayList<IPersonaje>();
// Agregar los elementos a una lista
personajes.add(g1);
personajes.add(g2);
personajes.add(m1);
personajes.add(m2);
// Creamos el Visitor y le pasamos la lista para equiparlos con armas
IVisitor visitorArma = new EquiparArma();
visitorArma.visit( personajes );
// Creamos el Visitor y le pasamos la lista para equipar con armaduras a los guerreros
IVisitor visitorConjuro = new EquiparConjuro();
visitorConjuro.visit( personajes );
// Comprobar el resultado:
System.out.println( "Arma del guerrero g1: " + g1.getArma() );
System.out.println( "Arma del guerrero g2: " + g2.getArma() );
System.out.println( "Arma del mago m1: " + m1.getArma() );
System.out.println( "Arma del mago m1: " + m2.getArma() + "\n");
System.out.println( "Conjuro del mago m1: " + m1.getConjuro() );
System.out.println( "Conjuro del mago m1: " + m2.getConjuro() + "\n");
}
}
IVisitor.java:
package Visitor02;
import java.util.List;
public interface IVisitor
{
public void visit( Mago m );
public void visit( Guerrero g );
public void visit( List<IPersonaje> elementList );
}
EquiparArma.java (un VisitorConcreto según el diagrama anterior):
package Visitor02;
import java.util.List;
public class EquiparArma implements IVisitor
{
@Override
public void visit( Mago m )
{
m.setArma("DAGA");
}
// ------------------------------
@Override
public void visit( Guerrero g )
{
g.setArma("ESPADA");
}
// ------------------------------
@Override
public void visit( List<IPersonaje> personajes )
{
for( IPersonaje p : personajes )
{
p.accept(this);
}
}
}
EquiparConjuro.java (un VisitorConcreto según el diagrama anterior):
package Visitor02;
import java.util.List;
public class EquiparConjuro implements IVisitor
{
@Override
public void visit( Mago m )
{
if( m.getNivelMagia() <= 5 )
{
m.setConjuro("Bola de Fuego");
}
else
{
m.setConjuro("Rayo de hielo");
}
}
// ------------------------------
@Override
public void visit( Guerrero g ) {
}
// ------------------------------
@Override
public void visit( List<IPersonaje> personajes )
{
for( IPersonaje p : personajes )
{
p.accept(this);
}
}
}
IPersonaje.java (un IElemento según el diagrama anterior):
package Visitor02;
public interface IPersonaje
{
public void accept( IVisitor visitor );
}
Guerrero.java (un ElementoConcreto según el diagrama anterior):
package Visitor02;
public class Guerrero implements IPersonaje
{
private String arma = "";
// ------------------------------
public Guerrero() {
}
// ------------------------------
public String getArma()
{
return this.arma;
}
// ------------------------------
public void setArma(String arma)
{
this.arma = arma;
}
// ------------------------------
@Override
public void accept( IVisitor visitor )
{
if( visitor.getClass().equals( EquiparArma.class ) )
{
visitor.visit(this);
}
}
}
Mago.java (un ElementoConcreto según el diagrama anterior):
package Visitor02;
public class Mago implements IPersonaje
{
private int nivelMagia = 1;
private String arma = "";
private String conjuro = "";
// ------------------------------
public Mago() {
}
// ------------------------------
public int getNivelMagia()
{
return this.nivelMagia;
}
// ------------------------------
public void setNivelMagia(int nivelMagia)
{
this.nivelMagia = nivelMagia;
}
// ------------------------------
public String getArma()
{
return this.arma;
}
// ------------------------------
public void setArma(String arma)
{
this.arma = arma;
}
// ------------------------------
public String getConjuro()
{
return this.conjuro;
}
// ------------------------------
public void setConjuro(String conjuro)
{
this.conjuro = conjuro;
}
// ------------------------------
@Override
public void accept( IVisitor visitor )
{
visitor.visit(this);
}
}
Al ejecutarlo obtendríamos como resultado:
EXPLICACIÓN:
- En este caso deseamos que los objetos Guerrero puedan ser equipados con armas pero no con conjuros, los cuales sólo podrán equipados por objetos Mago.
- Para ello definimos una condición en el método accept() de la clase Guerrero de modo que sólo admita objetos que sean específicamente de la clase EquiparArma, llamando luego a su método visit().
- Por otro lado, el método visit() de la clase EquiparConjuro se utiliza para equipar con un conjuro diferente a los objetos de tipo Mago dependiendo de su nivel de magia.
En los ejemplos anteriores hemos omitido la creación de una clase de tipo ObjetoEstructura para facilitar la comprensión de cómo funciona este patrón de diseño. Veamos ahora un ejemplo en el que sí la implementamos.
En el siguiente programa tenemos una clase GrupoFacturas (de tipo ObjetoEstructura) en el que se guardan referencias a objetos Factura (que a su vez contienen otros de tipo Articulo). Al ejecutarlo se mostrarán las facturas que se hayan agregado junto con los artículos que contienen.
Main.java (un Cliente según el diagrama anterior):
package Visitor03;
public class Main
{
public static void main(String[] args)
{
// Creamos el objeto estructura
GrupoFacturas grupo = new GrupoFacturas();
// Creamos una factura y le pasamos los elementos
Factura fact1 = new Factura( 1 );
fact1.agregarArticulo( new Articulo("Tornillos", 40) );
fact1.agregarArticulo( new Articulo("Tuercas", 80) );
fact1.agregarArticulo( new Articulo("Taladros", 65) );
// Creamos otra factura y le pasamos los elementos
Factura fact2 = new Factura( 2 );
fact2.agregarArticulo( new Articulo("Martillos", 30) );
fact2.agregarArticulo( new Articulo("Linternas", 90) );
// Agregamos las facturas al objeto estructura
grupo.agregarFactura( fact1 );
grupo.agregarFactura( fact2 );
// Pasamos el visitor al objeto de estructura para que recorra
// todas las facturas y los artículos que contiene
grupo.accept( new Mostrar() );
}
}
GrupoFacturas.java (un ObjetoEstructura según el diagrama anterior):
package Visitor03;
import java.util.ArrayList;
public class GrupoFacturas implements IElemento
{
ArrayList<Factura> facturas = new ArrayList<Factura>();
// ------------------------------
public GrupoFacturas() {
}
// ------------------------------
public void agregarFactura( Factura factura )
{
this.facturas.add( factura );
}
// ------------------------------
public Factura getFactura( int posicion )
{
return this.facturas.get( posicion );
}
// ------------------------------
@Override
public void accept( IVisitor visitor )
{
for( Factura f : this.facturas )
{
f.accept(visitor);
}
}
}
IVisitor.java:
package Visitor03;
public interface IVisitor
{
public void visit( Factura factura );
public void visit( Articulo art );
}
Mostrar.java (un VisitorConcreto según el diagrama anterior):
package Visitor03;
public class Mostrar implements IVisitor
{
public Mostrar() {
}
// ------------------------------
@Override
public void visit( Factura fact )
{
System.out.println("=========================");
System.out.println( "Factura número [" + fact.getCodigo() + "]" );
}
// ------------------------------
@Override
public void visit( Articulo art )
{
System.out.println( "Artículo [" + art.getNombre() + "] --> [" + art.getUnidades() + "] unidades)" );
}
}
IElemento.java:
package Visitor03;
public interface IElemento
{
public void accept( IVisitor visitor );
}
Factura.java (un ElementoConcreto según el diagrama anterior):
package Visitor03;
import java.util.ArrayList;
public class Factura implements IElemento
{
int codigo;
ArrayList<Articulo> articulos = new ArrayList<Articulo>();
// ------------------------------
public Factura( int codigo )
{
this.setCodigo( codigo );
}
// ------------------------------
public int getCodigo()
{
return this.codigo;
}
// ------------------------------
public void setCodigo( int codigo )
{
this.codigo = codigo;
}
// ------------------------------
public void agregarArticulo( Articulo art)
{
this.articulos.add( art );
}
// ------------------------------
@Override
public void accept( IVisitor visitor )
{
// Procesar el visitor para la factura
visitor.visit(this);
// Procesar el visitor para cada artículo en la factura
for( Articulo art : this.articulos )
{
art.accept(visitor);
}
}
}
Articulo.java (un ElementoConcreto según el diagrama anterior):
package Visitor03;
public class Articulo implements IElemento
{
private String nombre = "";
private int unidades;
// ------------------------------
public Articulo( String nombre, int unidades )
{
this.setNombre( nombre );
this.setUnidades( unidades );
}
// ------------------------------
public String getNombre()
{
return this.nombre;
}
// ------------------------------
public void setNombre(String nombre)
{
this.nombre = nombre;
}
// ------------------------------
public int getUnidades()
{
return this.unidades;
}
// ------------------------------
public void setUnidades(int unidades)
{
this.unidades = unidades;
}
// ------------------------------
@Override
public void accept( IVisitor visitor )
{
visitor.visit(this);
}
}
Al ejecutarlo obtendríamos como resultado:
EXPLICACIÓN:
- Al principio del programa creamos dos objetos de tipo Factura agregándoles los de tipo Articulo (de tipo IElemento ambos).
- A continuación creamos un objeto GrupoFacturas (de tipo ObjetoEstructura) y le agregamos cada objeto de tipo Factura creado.
- Observa que la clase GrupoFacturas implementa la interface IElemento, al igual que Factura y Articulo.
- Después se pasa el objeto de tipo Mostrar como parámetro al método accept() de GrupoFacturas, en el cual se recorrerán todas las facturas para enviar dicho objeto a cada una de ellas.
- En el método accept() de cada Factura se llama a su vez al método visit() de Mostrar (el VisitorConcreto) enviándole como parámetro el propio objeto Factura, y posteriormente cada uno de los objeto Articulo que contiene.
- En dicho método se mostrará el código de la factura por un lado, y la información de los artículos (nombre y unidades) por otro.