A Developer's Diary

Oct 8, 2012

Prototype Design Pattern

Definition
A creational pattern which allows creation of new instances by copying existing instance or prototype.

Applicability
1. When the classes to instantiate are specified at the run time
2. When creating a new instance is more expensive due to high computation or a call over the network

Example
Below is a simple game simulation whose characters are borrowed from the multiplayer FPS game Quake III Arena.

1. For playing the game, you have to select from among the three prototype players available at your disposal. These players are having diverse characteristics ranging from attitude, choice of gender, body, weapons preference, movements, skill level and a lot more. As such, it becomes a time consuming operation to create a new player instance each time from the store. This problem can be addressed by having a player registry which loads the prototypes during the game initialization time and the copy of the prototypes are then used for playing the game

2. A user can configure the game to have X number of human players and Y number of computer players. The computer players are chosen at random and the client is unaware of the type of prototype being used to initialize the computer player. This involves selecting a prototype randomly from the array and using the clone() method to return a copy of the self.

A sequence diagram roughly explaining the flow of calls in the gaming application

package patterns.example.prototype;

/**
 *File: Main.java (Game Launcher)
 */

public class Main
{
    private static Game game = new Game();

    public static void main(String[] args) {
        game.init();
        game.configure();
        game.start();
        game.play();
    }
}

package patterns.example.prototype;

/**
 * File: PlayerRegistry.java
 *
 * Responsible for loading the prototypes during the game initialization and
 * for delegating the calls to the appropriate prototypes for cloning
 */

public class PlayerRegistry
{
    private static final int NUMBER_OF_PROTOTYPES = 3;
    private Prototype[] playersRegistry = null;

    enum Player {
        SORLAG(0), XAERO(1), DOOM(2);

        private final int value;

        private Player(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

    public PlayerRegistry() {
        playersRegistry = new Prototype[NUMBER_OF_PROTOTYPES];
    }

    public Prototype getRandomPlayer() {
        int idx = (int) (NUMBER_OF_PROTOTYPES * Math.random());
        return playersRegistry[idx].clone();
    }

    public Prototype getPlayer(Player player) {
        return playersRegistry[player.getValue()].clone();
    }

    public void loadPrototypes() {
        playersRegistry[Player.SORLAG.getValue()] = new Sorlag();
        playersRegistry[Player.XAERO.getValue()] = new Xaero();
        playersRegistry[Player.DOOM.getValue()] = new Doom();
    }
}

package patterns.example.prototype;

/**
 * File: Prototype.java
 *
 * The Prototype interface has two contract methods. You can ignore the 
 * play() method as it has been introduced for making the example complete
 */

public interface Prototype
{
    public Prototype clone();
    public void play();
}

package patterns.example.prototype;

/**
 * File: Copyable.java
 */

public interface Copyable
{
    public Copyable deepCopy();
}

package patterns.example.prototype;

/**
 * File: PlayerDetails.java
 */

public class PlayerDetails implements Copyable
{
    private String gender = null;
    private String face = null;
    private String bodyType = null;
    private int noOfHands = 0;
    private int noOfLegs = 0;

    public String getGender() {
        return gender;
    }

    public PlayerDetails setGender(String gender) {
        this.gender = gender;
        return this;
    }

    public String getFace() {
        return face;
    }

    public PlayerDetails setFace(String face) {
        this.face = face;
        return this;
    }

    public int getNoOfHands() {
        return noOfHands;
    }

    public PlayerDetails setNoOfHands(int noOfHands) {
        this.noOfHands = noOfHands;
        return this;
    }

    public int getNoOfLegs() {
        return noOfLegs;
    }

    public PlayerDetails setNoOfLegs(int noOfLegs) {
        this.noOfLegs = noOfLegs;
        return this;
    }

    public String getBodyType() {
        return bodyType;
    }

    public PlayerDetails setBodyType(String bodyType) {
        this.bodyType = bodyType;
        return this;
    }

    public Copyable deepCopy() {
        PlayerDetails copy = new PlayerDetails();
        copy.setGender(this.gender).setBodyType(this.bodyType)
            .setFace(this.face).setNoOfHands(this.noOfHands)
            .setNoOfLegs(this.noOfLegs);
        return copy;
    }

}

package patterns.example.prototype;

/**
 * File: Doom.java
 * 
 * A concrete prototype class
 */

public class Doom implements Prototype
{
    private String name = "DOOM";
    private Copyable playerDetails = null;

    public Doom() {
        loadPlayerDetails();
    }

    private void loadPlayerDetails() {
        playerDetails = new PlayerDetails().setGender("MALE")
            .setBodyType("STRONG").setFace("ROUND").setNoOfHands(2)
            .setNoOfLegs(2);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Copyable getDetails() {
        return playerDetails;
    }

    private void setDetails(Copyable details) {
        this.playerDetails = details;
    }
    
    public void play()
    {
        System.out.println("DOOM: firing railgun");
    }

    @Override
    public Prototype clone() {
        Doom clone = new Doom();
        clone.setDetails(this.playerDetails.deepCopy());
        return clone;
    }
}

package patterns.example.prototype;

/**
 * File: Sorlag.java
 * 
 * A concrete prototype class
 */

public class Sorlag implements Prototype
{
    private String name = "SORLAG";
    private Copyable playerDetails = null;

    public Sorlag() {
        loadPlayerDetails();
    }

    private void loadPlayerDetails() {
        playerDetails = new PlayerDetails().setGender("MALE")
            .setBodyType("ALIEN").setFace("SQUARE").setNoOfHands(4)
            .setNoOfLegs(4);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Copyable getDetails() {
        return playerDetails;
    }

    private void setDetails(Copyable details) {
        this.playerDetails = details;
    }
    
    public void play()
    {
        System.out.println("SORLAG: jumping...");
    }

    @Override
    public Prototype clone() {
        Sorlag clone = new Sorlag();
        clone.setDetails(this.playerDetails.deepCopy());
        return clone;
    }
}

package patterns.example.prototype;

/**
 * File: Xaero.java
 * 
 * A concrete prototype class
 */

public class Xaero implements Prototype
{
    private String name = "Xaero";
    private Copyable playerDetails = null;

    public Xaero() {
        loadPlayerDetails();
    }

    private void loadPlayerDetails() {
        playerDetails = new PlayerDetails().setGender("MALE")
            .setBodyType("ATHLETIC").setFace("OVAL").setNoOfHands(2)
            .setNoOfLegs(2);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Copyable getDetails() {
        return playerDetails;
    }

    private void setDetails(Copyable details) {
        this.playerDetails = details;
    }
    
    public void play()
    {
        System.out.println("XAERO: firing machinegun...");
    }

    @Override
    public Prototype clone() {
        Xaero clone = new Xaero();
        clone.setDetails(this.playerDetails.deepCopy());
        return clone;
    }
}

package patterns.example.prototype;

import patterns.example.prototype.PlayerRegistry.Player;

/**
 * File: Game.java
 */

public class Game
{

    private PlayerRegistry playerRegistry;
    private Prototype[] players;
    private int max_players;
    private int num_of_bots;

    public void init() {
        System.out.println("Loading Game...");
        playerRegistry = new PlayerRegistry();
        playerRegistry.loadPrototypes();
    }

    public void configure() {
        System.out.println("Loading Configuration...");
        setMaxPlayers(10);
        setBotCount(9);
    }

    private void setBotCount(int botCount) {
        num_of_bots = botCount;
    }

    private void setMaxPlayers(int playerCount) {
        max_players = playerCount;
        players = new Prototype[playerCount];
    }

    public void start() {
        System.out.println("Starting the game...");
        loadPlayers();
    }

    private void loadPlayers() {
        int num_of_humans = max_players - num_of_bots;

        int idx = 0;
        for (int i = 0; i < num_of_bots; ++i) {
            players[idx++] = playerRegistry.getRandomPlayer();
        }

        for (int i = 0; i < num_of_humans; ++i) {
            players[idx++] = playerRegistry.getPlayer(Player.XAERO);
        }
    }

    public void play() {
        for (int i = 0; i < players.length; ++i) {
            players[i].play();
        }
    }
}

More on Prototype Pattern:
Stevey's Blog Rant's

1 comment :

Anonymous said...

This topic is something that I have been looking into for a while now and your insight is exceptional. Thanks for sharing this information.
Design Prototype

Post a Comment