Este patrón nos será útil si necesitamos crear y manipular copias de otros objetos:
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á:

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:

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