Skip to content
Related Articles

Related Articles

Improve Article
Save Article
Like Article

Java Pattern Prototype on Game Server

  • Difficulty Level : Easy
  • Last Updated : 18 Jan, 2022

Sometimes also known as cloning, basically the prototype is a generative design pattern that allows you to copy objects without making your code dependent on their classes.

Idea

The idea is to create a pack of monsters that will attack our hero/character. A specific configuration is set for enemies – Enemy config. With the help of the prototype, we will be able to copy the basic enemies and make groups of them. We can also copy groups and make up large units of them. We can set parameters, for example, 20 melee enemies and 40 ranged enemies. Based on the basic configuration, we can create copies of the units. That will further allow us to clone entire units. Cool, isn’t it?

Problem

Let’s say you have an Enemy object and you want to create an exact copy of it. How would you do it? First, we need to create a new object of the same class. Then you need to go through all the fields of the original object and copy their values to the new object.



Fine! But there is a catch. Not all objects can be copied in this way because some fields may be closed and not visible from outside the object itself.

There is another problem with the direct approach. Since we need to know the object’s class to create a duplicate, our code becomes dependent on that class. If other addiction doesn’t scare us, there is another snag. Sometimes you only know the interface that an object follows, but not its specific class, when, for example, a parameter in a method accepts any objects that follow some interface.

Solution

The Prototype template delegates the cloning process to natural cloned objects. The template declares a standard interface for all objects that support cloning. This interface allows you to clone an object without binding any code to the class of that object. Usually, such an interface contains only one clone method.

The implementation of the clone method in all classes is very similar. The method creates an object of the current class and transfers all the field values of the old object to the new one. You can even copy private fields because most programming languages allow objects to access the private fields of other objects belonging to the same class.

An object that supports cloning is called a prototype. When your objects have dozens of fields and hundreds of possible configurations, cloning them can be an alternative to subclassing.

Here’s how it works: you create a set of objects that are customizable in various ways. When you need an object like the one you customized, you clone the prototype instead of creating a new object from scratch.

Applicability



 Use the prototype pattern when your code does not need to depend on the specific classes of objects that you need to copy.

This often happens when your code works with objects passed to you from third-party code through some interface. The specific classes of these objects are unknown, and you could not depend on them even if you wanted to.

The prototype template provides a common interface for client code to work with all objects that support cloning. This interface makes the client code independent of the specific classes of cloned objects.

The Prototype pattern allows you to use a set of pre-created objects, configured in various ways as prototypes.

Instead of instantiating a subclass to match some configuration, the client can find the appropriate prototype and clone it.

How to implement

Create a prototype interface and declare a clone method in it. Or add a method to all classes of the existing class hierarchy if you have one.


The prototype class must define an alternative constructor that takes an object of that class as an argument. The constructor must copy the values ​​of all fields defined in the class from the passed object to the newly created instance. If you change the subclass, you must call the parent constructor to let the superclass handle cloning of its private fields. If your programming language does not support method overloading, you can define a special method to copy object data. The constructor is a more convenient place because it delivers the resulting object immediately after the call to the new operator.

The cloning method usually consists of one line: starting a new statement with a prototype constructor. Note that each class must explicitly override the clone method and use its class name along with the new operator. Otherwise, the clone method can create an object of the parent class.


Optionally, you can create a centralized prototype registry to store a catalog of frequently used prototypes. Using a static prototype fetch method, you can implement the registry as a new factory class or place it in the prototype base class. This method should search for a prototype based on the search criteria that the client code passes to the method. The requirements can be either a simple string tag or a complex set of search parameters. Once a matching prototype is found, the registry must clone it and return it to the client. Finally, replace direct calls to subclass constructors with calls to the prototype registry factory method.




Copying enemies

Let’s see how you can implement a prototype without the standard Cloneable interface.

Step 1: Let’s create a basic abstract class Enemy

Example 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);
   }
}


Step 2: We need several enemies. Let it be ArcherEnemy and MeleeEnemy

Example 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);
   }
}


 
Example 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);
    }
}


  Step 3: Let’s test the creation of clones as we have basic enemies ready.

Example 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!)");
            }
        }
    }
}


Output:

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

We got a list of enemies that we can copy and transfer to the client, if necessary. With this approach, we have the opportunity to use already created enemies and combine them into different groups, storing them in complex data structures. 




My Personal Notes arrow_drop_up
Recommended Articles
Page :

Start Your Coding Journey Now!