A veces también conocido como clonación, básicamente el prototipo es un patrón de diseño generativo que le permite copiar objetos sin hacer que su código dependa de sus clases.
Ocurrencia
La idea es crear una manada de monstruos que ataquen a nuestro héroe/personaje. Se establece una configuración específica para los enemigos: Enemy config . Con la ayuda del prototipo, podremos copiar los enemigos básicos y agruparlos. También podemos copiar grupos y formar grandes unidades de ellos. Podemos establecer parámetros, por ejemplo, 20 enemigos cuerpo a cuerpo y 40 enemigos a distancia. Basándonos en la configuración básica, podemos crear copias de las unidades. Eso nos permitirá además clonar unidades enteras. Genial, ¿no?
Problema
Digamos que tienes un objeto Enemigo y quieres crear una copia exacta de él. ¿Como lo harias? Primero, necesitamos crear un nuevo objeto de la misma clase. Luego, debe revisar todos los campos del objeto original y copiar sus valores en el nuevo objeto .
¡Multa! Pero hay una trampa. No todos los objetos se pueden copiar de esta manera porque algunos campos pueden estar cerrados y no ser visibles desde fuera del propio objeto.
Hay otro problema con el enfoque directo. Dado que necesitamos conocer la clase del objeto para crear un duplicado, nuestro código se vuelve dependiente de esa clase. Si otra adicción no nos asusta, hay otro inconveniente. A veces, solo conoce la interfaz que sigue un objeto, pero no su clase específica, cuando, por ejemplo, un parámetro en un método acepta cualquier objeto que siga alguna interfaz.
Solución
La plantilla Prototype delega el proceso de clonación a objetos clonados naturales. La plantilla declara una interfaz estándar para todos los objetos que admiten la clonación. Esta interfaz le permite clonar un objeto sin vincular ningún código a la clase de ese objeto. Por lo general, dicha interfaz contiene solo un método de clonación.
La implementación del método clon en todas las clases es muy similar. El método crea un objeto de la clase actual y transfiere todos los valores de campo del objeto antiguo al nuevo. Incluso puede copiar campos privados porque la mayoría de los lenguajes de programación permiten que los objetos accedan a los campos privados de otros objetos que pertenecen a la misma clase.
Un objeto que admite la clonación se denomina prototipo . Cuando sus objetos tienen docenas de campos y cientos de configuraciones posibles, clonarlos puede ser una alternativa a la creación de subclases.
Así es como funciona: crea un conjunto de objetos que se pueden personalizar de varias maneras. Cuando necesita un objeto como el que personalizó, clona el prototipo en lugar de crear un nuevo objeto desde cero.
Aplicabilidad
Utilice el patrón prototipo cuando su código no necesite depender de las clases específicas de objetos que necesita copiar .
Esto sucede a menudo cuando su código funciona con objetos que le pasan desde un código de terceros a través de alguna interfaz. Las clases específicas de estos objetos son desconocidas, y no podrías depender de ellos aunque quisieras.
La plantilla prototipo proporciona una interfaz común para que el código del cliente funcione con todos los objetos que admiten la clonación. Esta interfaz hace que el código del cliente sea independiente de las clases específicas de objetos clonados.
El patrón Prototipo le permite usar un conjunto de objetos creados previamente , configurados de varias maneras como prototipos.
En lugar de instanciar una subclase para que coincida con alguna configuración, el cliente puede encontrar el prototipo adecuado y clonarlo.
Como implementar
Cree una interfaz prototipo y declare un método de clonación en ella. O agregue un método a todas las clases de la jerarquía de clases existente si tiene una.
La clase prototipo debe definir un constructor alternativo que tome un objeto de esa clase como argumento. El constructor debe copiar los valores de todos los campos definidos en la clase del objeto pasado a la instancia recién creada. Si cambia la subclase, debe llamar al constructor principal para permitir que la superclase maneje la clonación de sus campos privados. Si su lenguaje de programación no admite la sobrecarga de métodos, puede definir un método especial para copiar datos de objetos. El constructor es un lugar más conveniente porque entrega el objeto resultante inmediatamente después de la llamada al nuevo operador.
El método de clonación generalmente consta de una línea: comenzar una nueva declaración con un constructor prototipo. Tenga en cuenta que cada clase debe anular explícitamente el método de clonación y usar su nombre de clase junto con el operador nuevo. De lo contrario, el método de clonación puede crear un objeto de la clase principal.
Opcionalmente, puede crear un registro de prototipos centralizado para almacenar un catálogo de prototipos de uso frecuente. Con un método de recuperación de prototipo estático, puede implementar el registro como una nueva clase de fábrica o colocarlo en la clase base de prototipo. Este método debe buscar un prototipo basado en los criterios de búsqueda que el código del cliente pasa al método. Los requisitos pueden ser una etiqueta de string simple o un conjunto complejo de parámetros de búsqueda. Una vez que se encuentra un prototipo coincidente, el registro debe clonarlo y devolverlo al cliente. Finalmente, reemplace las llamadas directas a los constructores de subclases con llamadas al método de fábrica de registro prototipo.
Copiando enemigos
Veamos cómo puede implementar un prototipo sin la interfaz clonable estándar.
Paso 1: Vamos a crear una clase abstracta básica Enemigo
Ejemplo 1:
Java
// Java Program to Create Abstract Enemy class // Abstract class public abstract class Enemy { // enemy health public int health; // enemy speed public int speed; // enemy name public String name; // Constructor 1 // Base constructor public Enemy() { } // Constructor 2 // Copy constructor public Enemy(Enemy target) { // Check that target not empty if (target != null) { // set base params and note // this keyword refers to current instance itself this.health = target.health; this.speed = target.speed; this.name = target.name; } } // clone() method public abstract Enemy clone(); // Override equals() method @Override public boolean equals(Object o) { if (!(o instanceof Enemy)) return false; Enemy enemy = (Enemy) o; return enemy.health == health && enemy.speed == speed && Objects.equals(enemy.name, name); } // Override hashCode() method @Override public int hashCode() { return Objects.hash(health, speed, name); } }
Paso 2: Necesitamos varios enemigos. Que sea ArcherEnemy y MeleeEnemy
Ejemplo 2-A
Java
// Let`s create ArcherEnemy with attack range public class ArcherEnemy extends Enemy { // Enemy is a parent public int attackRange; // add new param public ArcherEnemy() { } public ArcherEnemy(ArcherEnemy target) { // clone constructor super(target); // first of all clone base Enemy if (target != null) { this.attackRange = target.attackRange; // then clone additional params } } /* Clone based on THIS enemy */ @Override public Enemy clone() { return new ArcherEnemy(this); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; ArcherEnemy that = (ArcherEnemy) o; return attackRange == that.attackRange ; } @Override public int hashCode() { return Objects.hash(super.hashCode(), attackRange); } }
Ejemplo 2-B
Java
// Let`s create MeleeEnemy with 'melee params' public class MeleeEnemy extends Enemy { public int blockChance; // add new param public boolean withShield; // add new param public MeleeEnemy() {} // Create clone constructor // clone base Enemy // and then clone additional params public MeleeEnemy(MeleeEnemy target) { super(target); if (target != null) { this.blockChance = target.blockChance; this.withShield = target.withShield; } } // Clone class based on current values @Override public Enemy clone() { return new MeleeEnemy(this); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; MeleeEnemy that = (MeleeEnemy)o; return blockChance == that.blockChance && withShield == that.withShield; } @Override public int hashCode() { return Objects.hash(super.hashCode(), blockChance, withShield); } }
Paso 3: Probemos la creación de clones ya que tenemos enemigos básicos listos.
Ejemplo 3:
Java
// Java Program to Illustrate Creation of Clones // Main class public class Demo { // Method 2 // Main driver method public static void main(String[] args) { // Creating enemy list List<Enemy> enemyList = new ArrayList<>(); // Creating enemy list copy List<Enemy> enemyListCopy = new ArrayList<>(); // Create baseArcher ArcherEnemy baseArcher = new ArcherEnemy(); // Setting attributes baseArcher.health = 150; baseArcher.speed = 35; baseArcher.name = "Base Archer"; baseArcher.attackRange = 100; enemyList.add(baseArcher); // Create clone baseArcher ArcherEnemy baseArcherClone = (ArcherEnemy)baseArcher.clone(); // Adding clone to enemyList enemyList.add(baseArcherClone); // Create baseMeleeEnemy MeleeEnemy baseMeleeEnemy = new MeleeEnemy(); // Setting attributes baseMeleeEnemy.health = 10; baseMeleeEnemy.speed = 20; baseMeleeEnemy.name = "blue"; baseMeleeEnemy.blockChance = 7; baseMeleeEnemy.withShield = true; // Now adding baseMeleeEnemy to enemyList // using add() method enemyList.add(baseMeleeEnemy); // Cloning whole list and comparing cloneAndCompare(enemyList, enemyListCopy); } // Method 1 // To clone and compare private static void cloneAndCompare(List<Enemy> enemyList, List<Enemy> enemyListCopy) { // Iterate over enemyList // using for-each loop for (Enemy enemy : enemyList) { // Clone enemys and add into enemyListCopy enemyListCopy.add(enemy.clone()); } // Compare enemys in enemyList and in enemyListCopy for (int i = 0; i < enemyList.size(); i++) { // Checking that enemy and cloneEnemy have // different links if (enemyList.get(i) != enemyListCopy.get(i)) { // Simply printing the result System.out.println( i + ": Enemy are different objects (yay!)"); // Check that they have same params if (enemyList.get(i).equals( enemyListCopy.get(i))) { // Print statement if they are identical System.out.println( i + ": And they are identical (yay!)"); } else { // Print statement if they are // not-identical System.out.println( i + ": But they are not identical (booo!)"); } } else { // Print statement if Shape objects are same System.out.println( i + ": Shape objects are the same (booo!)"); } } } }
Producción:
0: Enemy are different objects (yay!) // have different links 0: And they are identical (yay!) // but have same inner state 1: Enemy are different objects (yay!) // have different links 1: And they are identical (yay!) // but have same inner state 2: Enemy are different objects (yay!) // have different links 2: And they are identical (yay!) // but have same inner state
Tenemos una lista de enemigos que podemos copiar y transferir al cliente, si es necesario. Con este enfoque, tenemos la oportunidad de usar enemigos ya creados y combinarlos en diferentes grupos, almacenándolos en estructuras de datos complejas.