informaticaPC

Patrones de Diseño Software

Prototype

Este patrón nos será útil si necesitamos crear y manipular copias de otros objetos:

Prototype en UML

Cuando nos disponemos a clonar un objeto es importante tener en cuenta si guarda referencias de otros o no. En este punto hay que distinguir las siguientes formas de replicarlos:

  • Copia superficial: el objeto clonado tendrá los mismos valores que el original, guardando también referencias a otros objetos que contenga (por lo que si son modificados desde el objeto original o desde alguno de sus clones el cambio afectará a todos ellos).
  • Copia profunda: el objeto clonado tendrá los mismos valores que el original así como copias de los objetos que contenga el original (por lo que si son modificados por cualquiera de ellos, el resto no se verán afectados).

Veamos un ejemplo sencillo en el que creamos un clon (mediante copia superficial) de un personaje dentro de un juego:

Main.java:

package Prototype01;

public class Main
{
    public static void main(String[] args)
    {
        GestorEnemigo objGP = new GestorEnemigo();

        // Obtenemos el Guerrero original
        Enemigo g1 = objGP.getEnemigo("Warrior1");

        System.out.println("==============================");

        // Mostramos su datos
        System.out.println("El Guerrero original se llama [" + g1.getNombre() + "]");
        System.out.println("Su arma es [" + g1.getArma() + "]");

        System.out.println("==============================");

        // Obtener un segundo Guerrero (clon del anterior)
        Enemigo g2 = objGP.getClon("Warrior1");

        // Mostrar los datos  (ambos tienen datos similares)
        System.out.println("Clon del Guerrero creado:");
        System.out.println("Su nombre es [" + g2.getNombre() + "]");
        System.out.println("Su arma es [" + g2.getArma() + "]");

        System.out.println("==============================");

        // Modificamos el Guerrero clonado
        g2.setNombre("Warrior2");
        g2.setArma("HACHA");

        // Mostrar los datos de ambos Guerreros (ambos tienen diferentes datos)
        System.out.println("Tras modificar el clon, ahora se llama [" + g2.getNombre() + "]");
        System.out.println("Su arma es [" + g2.getArma() + "]\n");

        System.out.println("El nombre del Guerrero original es [" + g1.getNombre() + "]");
        System.out.println("Su arma es [" + g1.getArma() + "]");

        System.out.println("==============================");

        // Modificamos el Guerrero original
        g1.setNombre("Warrior-1");
        g1.setArma("MAZA");

        // Mostrar los datos de ambos Guerreros tras la modificación (ambos tienen diferentes datos)
        System.out.println("Tras modificar el original, ahora es [" + g1.getNombre() + "]");
        System.out.println("Su arma es [" + g1.getArma() + "]\n");

        System.out.println("El nombre del clon es [" + g2.getNombre() + "]");
        System.out.println("Su arma es [" + g2.getArma() + "]");
    }
}

GestorEnemigo.java (Cliente en el diagrama anterior):

package Prototype01;

import java.util.Hashtable;

public class GestorEnemigo
{
    // Para almacenar los objetos de tipo Prototype que se vayan creando
    private Hashtable hEnemigos = new Hashtable();

    // ------------------------------

    public GestorEnemigo()
    {
        // Crear un Hechicero y asignarle propiedades
        Enemigo h1 = new Hechicero();
        h1.setNombre("Sorcerer1");

        // Guardarlo
        this.addEnemigo( h1.getNombre(), h1 );

        // Crear un Guerrero y asignarle propiedades
        Enemigo g1 = new Guerrero();
        g1.setNombre("Warrior1");
        g1.setArma("ESPADA");

        // Guardarlo
        this.addEnemigo( g1.getNombre(), g1 );
    }

    // ------------------------------

    public void addEnemigo( String nombre, Enemigo objEnemigo )
    {
        this.hEnemigos.put( nombre, objEnemigo );
    }

    // ------------------------------

    public Enemigo getEnemigo( String nombre )
    {
        Enemigo objPrototipo = (Guerrero) hEnemigos.get( nombre );
        return objPrototipo;
    }

    // ------------------------------

    public Enemigo getClon( String nombre )
    {
        // Localizar el objeto deseado
        Enemigo objPrototipo = (Guerrero) hEnemigos.get( nombre );

        // Devolver un clon
        return (Enemigo) objPrototipo.clonar();
    }
}

Enemigo.java (Prototipo en el diagrama anterior):

package Prototype01;

public abstract class Enemigo
{
    private String nombre ="";
    private String arma = "DAGA";

    // ------------------------------

    public String getNombre() {
        return this.nombre;
    }

    // ------------------------------

    public void setNombre( String nombre) {
        this.nombre = nombre;
    }

    // ------------------------------

    public String getArma() {
        return this.arma;
    }

    // ------------------------------

    public void setArma( String arma) {
        this.arma = arma;
    }

    // ------------------------------

    // Métodos que deberán ser construídos por las clases que hereden de ésta
    public abstract Enemigo clonar();

    public abstract void atacar();
    public abstract void detener();
}

Guerrero.java (PrototipoConcreto1 en el diagrama anterior):

package Prototype01;

public class Guerrero extends Enemigo
{
    public Guerrero() {
        System.out.println("Guerrero creado");
    }

    // ------------------------------

    @Override
    public void atacar() {
        System.out.println("El guerrero ataca");
    }

    // ------------------------------

    @Override
    public void detener() {
        System.out.println("El guerrero se detiene");
    }

    // ------------------------------

    @Override
    public Enemigo clonar()
    {
        /*
         * Creamos una nueva instancia y le asignamos los valores actuales para
         * después devolverlo (es también de tipo 'Prototype' ya que hereda de él)
         */
        Guerrero objGuerrero = new Guerrero();

	objGuerrero.setNombre( this.getNombre() );
        objGuerrero.setArma( this.getArma() );

        return objGuerrero;
    }
}

Hechicero.java (PrototipoConcreto2 en el diagrama anterior):

package Prototype01;

public class Hechicero extends Enemigo
{
    public Hechicero() {
        System.out.println("Hechicero creado");
    }

    // ------------------------------

    @Override
    public void atacar() {
        System.out.println("El hechicero ataca");
    }

    // ------------------------------

    @Override
    public void detener() {
        System.out.println("El hechiero se detiene");
    }

    // ------------------------------

    @Override
    public Enemigo clonar()
    {
        /*
         * Creamos una nueva instancia y le asignamos los valores actuales para
         * después devolverlo (es también de tipo 'Prototype' ya que hereda de él)
         */
        Hechicero objHechicero = new Hechicero();

	objHechicero.setNombre( this.getNombre() );
        objHechicero.setArma( this.getArma() );

        return objHechicero;
    }
}

EXPLICACIÓN:

  • Al inicio del programa se crea una instancia de la clase GestorPrototype que a su vez se encargará de crear dos personajes de tipo Prototype (un Hechicero y un Guerrero), asignando un nombre y equipando con un arma a cada uno de ellos, guardando también referencias a ellos.
  • La clase abstracta Enemigo implementa métodos comunes y define el método clonar() que deberan implementar las clases que hereden de ella (los personajes) para crear réplicas de sí mismos.
  • Comprueba que al modificarse las propiedades (nombre y arma) de un clon el objeto original no se ve afectado (y viceversa).

El resultado que obtendremos será:

Ejemplo

Información

Ya que estamos utilizando el lenguaje Java en los ejemplos sería más correcto hacer uso de la interface Cloneable y utilizar el método clone() para crear las réplicas (mediante copia superficial).

A continuación mostramos un ejemplo de cómo hacerlo (el archivo Main.java se mantendría igual).

GestorEnemigo.java (Cliente en el diagrama anterior):

package Prototype02;

import java.util.Hashtable;

public class GestorEnemigo
{
    // Para almacenar los objetos de tipo Prototype que se vayan creando
    private Hashtable hEnemigos = new Hashtable();

    // ------------------------------

    public GestorEnemigo()
    {
        // Crear un Hechicero y asignarle propiedades
        Enemigo h1 = new Hechicero();
        h1.setNombre("Sorcerer1");

        // Guardarlo
        this.addEnemigo( h1.getNombre(), h1 );

        // Crear un Guerrero y asignarle propiedades
        Enemigo g1 = new Guerrero();
        g1.setNombre("Warrior1");
        g1.setArma("ESPADA");

        // Guardarlo
        this.addEnemigo( g1.getNombre(), g1 );
    }

    // ------------------------------

    public void addEnemigo( String nombre, Enemigo objEnemigo )
    {
        this.hEnemigos.put( nombre, objEnemigo );
    }

    // ------------------------------

    public Enemigo getEnemigo( String nombre )
    {
        Enemigo objPrototipo = (Guerrero) hEnemigos.get( nombre );

        return objPrototipo;
    }

    // ------------------------------

    public Enemigo getClon( String nombre )
    {
        // Localizar el objeto deseado
        Enemigo objPrototipo = (Guerrero) hEnemigos.get( nombre );

        try {
            // Devolver un clon
            return (Enemigo) objPrototipo.clonar();
        } catch( CloneNotSupportedException e ) {

            System.out.println("Error al crear el clon, salimos del programa.");
            System.exit(0);
        }

        return null;
    }
}

Enemigo.java (Prototipo en el diagrama anterior):

package Prototype02;

public abstract class Enemigo implements Cloneable
{
    private String nombre ="";
    private String arma = "DAGA";

    // ------------------------------

    public String getNombre() {
        return this.nombre;
    }

    // ------------------------------

    public void setNombre( String nombre) {
        this.nombre = nombre;
    }

    // ------------------------------

    public String getArma() {
        return this.arma;
    }

    // ------------------------------

    public void setArma( String arma) {
        this.arma = arma;
    }

    // ------------------------------

    public Enemigo clonar() throws CloneNotSupportedException {
        // Es necesario hacer el typecast porque 'clone()' devuelve 'Object'
        return (Enemigo) this.clone();
    }

    // ------------------------------

    // Métodos que deberán ser construídos por las clases que hereden de ésta
    public abstract void atacar();
    public abstract void detener();
}

Guerrero.java (PrototipoConcreto1 en el diagrama anterior):

package Prototype02;

public class Guerrero extends Enemigo
{
    public Guerrero() {
        System.out.println("Guerrero creado");
    }

    // ------------------------------

    @Override
    public void atacar() {
        System.out.println("El guerrero ataca");
    }

    // ------------------------------

    @Override
    public void detener() {
        System.out.println("El guerrero se detiene");
    }
}

Hechicero.java (PrototipoConcreto2 en el diagrama anterior):

package Prototype02;

public class Hechicero extends Enemigo
{
    public Hechicero() {
        System.out.println("Hechicero creado");
    }

    // ------------------------------

    @Override
    public void atacar() {
        System.out.println("El hechicero ataca");
    }

    // ------------------------------

    @Override
    public void detener() {
        System.out.println("El hechiero se detiene");
    }
}

EXPLICACIÓN:

  • En este caso hemos eliminado el método clonar() de las clases Guerrero y Hechicero y lo hemos implementado en Enemigo (la clase abstracta de la que heredan), agregándole también la interface Cloneable.
  • Definimos el método clonar() de la clase abstracta Enemigo con 'throws CloneNotSupportedException' para que devuelva también la Excepción CloneNotSupportedException arrojada por el método clone() (se producirá error si se intenta replicar con dicho método un objeto de una clase que no tenga implementada la interface Cloneable).
  • En el método getClon() de GestorPrototype se solicita (dentro de un bloque 'try...catch' para capturar la excepción) un clon de un determinado objeto.

El resultado al ejecutar el programa será el mismo que en el primer ejemplo:

Ejemplo

Información

No es necesario implementar ningún método adicional al utilizar la interface Cloneable.

Primera página Anterior Siguiente Última página
Usamos cookies para ofrecerte una experiencia mejorada, el continuar navegando supone que aceptas su uso