Prototipo de patrón Java en Game Server

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. 

Publicación traducida automáticamente

Artículo escrito por deft1991 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *