Creational Pattern Series | Prototype

Ali Mohammad
7 min readApr 25, 2024

In this series we will take a look at the creational design patterns:

  1. Singleton
  2. Factory Method
  3. Abstract Factory
  4. Prototype
  5. Builder

❤️ What it is?

Prototype is a creational design pattern that lets you copy existing objects without making your code dependent on their classes.

The prototype design pattern is a creational design pattern that lets us make new things without starting from scratch by cloning or copying an existing object. By minimizing time-consuming activities like generating new objects, this pattern teaches you how to make the process simpler and faster.

A prototype object is used to implement the Prototype Design Pattern. An existing object that serves as a model for the production of new objects is called a prototype. We can create new objects by copying this prototype object and modifying its properties as necessary.

In TypeScript, we can implement the Prototype Design Pattern using the Object.create() method. This method creates a new object with the specified prototype.

🍪 The why?

Imagine you want to make an exact replica of an object you already own. What method would you use? You must first construct a fresh object of the same class. The next step is to go through the original object’s fields and copy their values across to the new object.

Lovely! There seems to be a price, though. Because some of the fields of an object could be private and not accessible from outside the object itself, not all objects can be cloned in that way.

The cloning procedure is delegated by the prototype pattern to the real objects that are being copied. All objects that support cloning will have a common interface, according to the pattern. This interface enables object cloning without tying your code to the object’s class. Such an interface typically just has one clone method.
All classes implement the clone method in a relatively similar way. The method makes a new object of the current class and copies all the field values from the old object to the new one. Because most programming languages allow objects to access the private fields of other objects that are members of the same class, you can even copy private fields.
A prototype is something that allows for cloning. If your objects have a lot of fields and lots of possible ways to set them up, cloning them might be a better choice than subclassing.

It works by creating a collection of things and configuring them in various ways is how it works. When you need an object that is similar to the one you configured, you can simply copy a prototype instead of creating a new one from scratch.

🫡 Structure:

interface Prototype {
clone(): Prototype;
}

class ConcretePrototype implements Prototype {
public property: string;
constructor(property: string) {
this.property = property;
}
public clone(): Prototype {
return Object.create(this);
}
}
const prototype = new ConcretePrototype('example');
const clonedPrototype = prototype.clone();

This example creates an interface called Prototype with a clone() method. We also define a concrete version of this interface called ConcretePrototype. It has a property field and implements the clone() method by calling Object. create(this).

Then, we make a ConcretePrototype instance with the name prototype and a property value of “example.” The clone() method is used to create a copy of the prototype that has the same properties as the original.
The Prototype Design Pattern is helpful when we need to develop numerous products that are very similar and have few distinguishing characteristics. By reducing pricey object formation processes, it can also be used to increase performance.

Consider creating an app that enables users to design unique characters for a game. Each character is distinct from the others in terms of things like name, skills, and appearance. We can avoid starting from scratch every time a user creates a new character thanks to the prototype design pattern. A prototype object that represents a fundamental character with default properties can be made. Afterwards, we may duplicate this prototype object and alter its characteristics as necessary to create new characters. By avoiding expensive processes, this technique can make it more simpler to create new objects and make them operate faster.
The construction of complex objects that demand a lot of initialization is another situation in which the Prototype Design Pattern might be helpful. Think about developing a financial application where you need complicated objects to represent financial products like stocks or bonds. Each financial instrument has a different set of characteristics, necessitating intricate initialization logic. We can make use of the Prototype Design Pattern rather than starting from scratch each time a new financial instrument is produced. A prototype object that represents a fundamental financial instrument and has initialization logic and default properties can be made. Next, to develop additional financial instruments, we can clone this prototype object and change its properties and initialization logic as necessary. By avoiding expensive procedures, this method can greatly reduce the complexity of producing new objects while also enhancing performance.

🍔 An Example:

// The abstract GameCharacter class that will serve as the prototype for all game characters
abstract class GameCharacter {
protected name: string;
protected health: number;
protected damage: number;

constructor(name: string, health: number, damage: number) {
this.name = name;
this.health = health;
this.damage = damage;
}
// The clone method that will be used to create copies of the GameCharacter prototype
public abstract clone(): GameCharacter;
public attack(target: GameCharacter): void {
target.health -= this.damage;
console.log(`${this.name} attacks ${target.name} for ${this.damage} damage!`);
if (target.health <= 0) {
console.log(`${target.name} has been defeated!`);
}
}
public getStatus(): string {
return `${this.name} has ${this.health} health remaining.`;
}
}
// The Concrete GameCharacter class that implements the clone method to create copies of itself
class Player extends GameCharacter {
public clone(): GameCharacter {
return new Player(this.name, this.health, this.damage);
}
}
// Another Concrete GameCharacter class that implements the clone method to create copies of itself
class Enemy extends GameCharacter {
public clone(): GameCharacter {
return new Enemy(this.name, this.health, this.damage);
}
}
// The GameCharacterFactory that creates and manages the game characters
class GameCharacterFactory {
private playerPrototype: GameCharacter;
private enemyPrototype: GameCharacter;
constructor() {
// Create the player prototype with initial stats
this.playerPrototype = new Player("Player", 100, 10);
// Create the enemy prototype with initial stats
this.enemyPrototype = new Enemy("Enemy", 50, 5);
}
public createPlayer(): GameCharacter {
// Use the player prototype to create a new player character
return this.playerPrototype.clone();
}
public createEnemy(): GameCharacter {
// Use the enemy prototype to create a new enemy character
return this.enemyPrototype.clone();
}
}
// The game logic that uses the GameCharacterFactory to create game characters and simulate battles
const factory = new GameCharacterFactory();
const player1 = factory.createPlayer();
const player2 = factory.createPlayer();
const enemy1 = factory.createEnemy();
const enemy2 = factory.createEnemy();
console.log(player1.getStatus()); // "Player has 100 health remaining."
console.log(enemy1.getStatus()); // "Enemy has 50 health remaining."
player1.attack(enemy1);
enemy1.attack(player1);
console.log(player1.getStatus()); // "Player has 95 health remaining."
console.log(enemy1.getStatus()); // "Enemy has 40 health remaining."

In this example, we have an abstract GameCharacter class that serves as the prototype for all game characters. It has a clone method that will be implemented by concrete subclasses to create copies of themselves. We also have two concrete subclasses Player and Enemy that implement the clone method.

We then have a GameCharacterFactory class that creates and manages the game characters. It has methods to create a new player character and a new enemy character by using the clone method on the respective prototypes.

Finally, we have some game logic that uses the GameCharacterFactory to create game characters and simulate battles between them. Each game character has a getStatus method that returns their current health status, and an attackmethod that reduces the target's health by the character's damage value.

🤯 When to use ?

  • When your code shouldn’t be dependent on the concrete classes of objects you need to copy, use the prototype pattern.
  • When you want to cut down on the amount of subclasses that merely differ in how they initialize their respective objects, use the pattern.✅

✅ Advantages

  • Cloned objects can be duplicated independently of their concrete classes.
  • By copying already-built prototypes, you may do away with repetitive initialization code.
  • Complex items can be produced more easily.
  • When working with complex object configuration presets, you have an alternative to inheritance.

❌ Drawbacks

  • The creation of a new object can be more expensive than other creational patterns due to the deep copy of the object being created.
  • The use of prototypes can make the code more complex and harder to understand.
  • If the prototype object is not properly initialized, the cloned objects may have unexpected behavior.
  • The prototype pattern can be less flexible than other creational patterns, as it requires that the objects being created have a common interface.

🚘 Conclusion:

In summary, the Prototype Design Pattern is a powerful tool that allows us to create new objects by cloning or copying an existing object. This pattern can significantly reduce the complexity of creating new objects and improve performance by avoiding costly operations. It is a useful pattern to keep in mind when building applications that require the creation of many similar objects or complex objects that require a lot of initialization.

You can find more explanations and code examples in my repository:

Hope you enjoyed the Article! ❤️

--

--