LLD - Prototype Design Pattern

LLD - Prototype Design Pattern

Understanding Prototype Pattern

Prototype Pattern comes under the Creational Design Pattern which caters to the creation of objects.
It enables cloning or copying data from the existing object and storing it in our newly created object.

Blueprint of Prototype Design Patten:

  1. Prototype Interface: To define a method for cloning

  2. Concrete Prototype: Classes that implement prototype interface to clone itself

  3. Client: Class that uses prototype to create new objects

Simplified UML Diagram

Case Study: Monsters in a Game

Deep Copy Implementation in a Game for Creating Monsters.

In many games, characters such as Monsters get stronger as the level of the game increases. In this scenario, the Prototype pattern can be used to create multiple instances of Monsters where their strength and other abilities increase as the game levels up.

Prototype Pattern is useful :

  1. When creating new objects is expensive or complicated

  2. When we have to create multiple variations of an object

  3. When we want to create a deep copy with nested fields

UML Diagram

Code

public interface Prototype {
    Prototype clone();
}
public class GameMonster implements Prototype {

    private String name;
    private MonsterType type;
    private int strength;
    private int level;
    private SecretStrength secretStrength;

    public GameMonster(String name, MonsterType type, int strength, int level, SecretStrength secretStrength) {
        this.name = name;
        this.type = type;
        this.strength = strength;
        this.level = level;
        this.secretStrength = secretStrength;
    }

    @Override
    public GameMonster clone() {
        var secretStrengthClone = secretStrength.clone();
        return new GameMonster(this.name, this.type, this.strength, this.level, secretStrengthClone);
    }

    public void setStrength(int strength) {
        this.level = strength;
    }

    public void setSecretStrength(SecretStrength secretStrength) {
        this.secretStrength = secretStrength;
    }

    public SecretStrength getSecretStrength() {
        return secretStrength;
    }

    @Override
    public String toString() {
        return "GameMonster{" +
                "name='" + name + '\'' +
                ", type=" + type +
                ", strength=" + strength +
                ", level=" + level +
                ", secretStrength=" + secretStrength +
                '}';
    }
}

public enum MonsterType {
    LAND,
    AIR
}
public class SecretStrength implements Prototype {
    private int secretPower;
    private int agility;
    private int speed;

    public SecretStrength(int secretPower, int agility, int speed) {
        this.secretPower = secretPower;
        this.agility = agility;
        this.speed = speed;
    }

    @Override
    public SecretStrength clone() {
        return new SecretStrength(this.secretPower, this.agility, this.speed);
    }

    public void upgrade(int gameLevel) {
        secretPower = this.secretPower * gameLevel;
        agility = this.agility * gameLevel;
        speed = this.speed * gameLevel;
    }

    @Override
    public String toString() {
        return "SecretStrength{" +
                "secretPower=" + secretPower +
                ", agility=" + agility +
                ", speed=" + speed +
                '}';
    }
}
import java.util.HashMap;
import java.util.Map;

public class MonsterRegistry {

    private static final Map<MonsterType, GameMonster> monsterMap = new HashMap<>();

    public static void addMonster(MonsterType type, GameMonster monster) {
        monsterMap.put(type, monster);
    }

    public static GameMonster getMonster(MonsterType type, int gameLevel) {
        var currentMonster = monsterMap.get(type);
        GameMonster cloneMonster = currentMonster.clone();

        cloneMonster.setStrength(gameLevel * 7); // increasing the strength as per game level
        cloneMonster.getSecretStrength().upgrade(gameLevel * 7);
        return cloneMonster;
    }
}
public class Main {
    public static void main(String[] args) {
        System.out.println("Implementing Prototype Pattern: ");

        // Creating Base Monster - Goblin and Dragon
        SecretStrength secretStrength = new SecretStrength(10, 20, 30);
        GameMonster greenGoblin = new GameMonster("Green Goblin", MonsterType.LAND, 10, 1, secretStrength);
        GameMonster dragon = new GameMonster("Draco", MonsterType.AIR, 20, 2, secretStrength);

        MonsterRegistry.addMonster(MonsterType.LAND, greenGoblin);
        MonsterRegistry.addMonster(MonsterType.AIR, dragon);

        System.out.println("Green Goblin: " + greenGoblin);
        System.out.println("Draco: " + dragon);

        // Upgrading Monsters for higher game level
        System.out.println("Level Up Monsters: ");
        GameMonster redGoblin = MonsterRegistry.getMonster(MonsterType.LAND, 4);
        GameMonster redDraco = MonsterRegistry.getMonster(MonsterType.AIR, 6);

        System.out.println("Level 4 Red Goblin: " + redGoblin);
        System.out.println("Level 4 Red Draco: " + redDraco);
    }
}

Github Source Code: LLD for Game Monster

Shallow vs Deep Copy

  1. Shallow Copy: It duplicates the object but only copies the reference of the nested object resulting in multiple shared states between the original and copied object

  2. Deep Copy: It duplicates the object and all the nested objects creating entirely independent instances, so changes to one will not affect the other

class Address {
    String street;

    Address(String street) {
        this.street = street;
    }
}

class Person {
    String name;
    Address address; // Reference to a mutable object

    Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    // Method to create a shallow copy
    Person shallowCopy() {
        return new Person(this.name, this.address); // Copies the reference of address
    }

    // Method to create a deep copy
    Person deepCopy() {
        return new Person(this.name, new Address(this.address.street)); // Creates a new Address object
    }
}

Refer: https://www.cs.utexas.edu/~scottm/cs307/handouts/deepCopying.htm