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 enemies 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.