informaticaPC

Patrones de Diseño Software


Regístrate

Tus datos no serán compartidos, solo nosotros te enviaremos información y novedades

Visitor

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.

Patrón Visitor en UML

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:

Ejemplo

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:

Ejemplo

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:

Ejemplo

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.
Primera página Anterior
Usamos cookies para ofrecerte una experiencia mejorada, el continuar navegando supone que aceptas su uso