Skip to main content

Prototype Pattern 🧬

Definition: The Prototype pattern allows you to create new objects by cloning existing instances (prototypes) rather than creating new instances from scratch.

🎯 Intent

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype. This pattern is useful when object creation is costly or complex.

🤔 Problem

You need to create objects that are similar to existing objects but with some modifications. Creating objects from scratch might be:

  • Expensive: Complex initialization or resource-intensive setup
  • Complex: Requires many parameters or configuration steps
  • Dynamic: Object types are determined at runtime
  • Configuration-heavy: Objects have many settings that vary slightly

💡 Solution

The Prototype pattern suggests creating objects by cloning existing instances rather than instantiating new ones. The pattern involves creating a prototype interface that declares a clone() method, and concrete classes implement this method to return copies of themselves.

🏗️ Structure

Prototype (interface)
└── clone()

ConcretePrototype implements Prototype
├── field1, field2, field3
└── clone() → return copy of this

Client
├── prototype: Prototype
└── operation() → prototype.clone()

💻 Simple Example

Document Templates

// Prototype interface
class DocumentPrototype {
clone() {
throw new Error('clone() method must be implemented');
}
}

// Concrete prototype - Resume document
class Resume extends DocumentPrototype {
constructor(name = '', experience = [], skills = [], template = 'classic') {
super();
this.type = 'Resume';
this.name = name;
this.experience = [...experience]; // Deep copy array
this.skills = [...skills]; // Deep copy array
this.template = template;
this.createdAt = new Date();
this.id = Math.random().toString(36).substr(2, 9);
}

clone() {
console.log(`🧬 Cloning resume: ${this.name || 'Unnamed'}`);

// Create a deep copy
const cloned = new Resume(
this.name,
this.experience.map((exp) => ({ ...exp })), // Deep copy objects in array
[...this.skills], // Copy array
this.template,
);

// Reset some fields for the clone
cloned.id = Math.random().toString(36).substr(2, 9);
cloned.createdAt = new Date();

console.log(`✅ Resume cloned with ID: ${cloned.id}`);
return cloned;
}

addExperience(company, position, years) {
this.experience.push({ company, position, years });
console.log(`💼 Added experience: ${position} at ${company}`);
}

addSkill(skill) {
if (!this.skills.includes(skill)) {
this.skills.push(skill);
console.log(`🎯 Added skill: ${skill}`);
}
}

setName(name) {
this.name = name;
console.log(`👤 Name set to: ${name}`);
}

display() {
console.log(`📄 Resume Details:`);
console.log(` ID: ${this.id}`);
console.log(` Name: ${this.name}`);
console.log(` Template: ${this.template}`);
console.log(` Experience: ${this.experience.length} jobs`);
console.log(` Skills: ${this.skills.join(', ')}`);
console.log(` Created: ${this.createdAt.toLocaleDateString()}`);
}
}

// Concrete prototype - Cover Letter document
class CoverLetter extends DocumentPrototype {
constructor(recipientCompany = '', position = '', template = 'formal') {
super();
this.type = 'Cover Letter';
this.recipientCompany = recipientCompany;
this.position = position;
this.template = template;
this.paragraphs = [];
this.createdAt = new Date();
this.id = Math.random().toString(36).substr(2, 9);
}

clone() {
console.log(`🧬 Cloning cover letter for: ${this.position} at ${this.recipientCompany}`);

const cloned = new CoverLetter(this.recipientCompany, this.position, this.template);

// Deep copy paragraphs
cloned.paragraphs = [...this.paragraphs];

// Reset some fields
cloned.id = Math.random().toString(36).substr(2, 9);
cloned.createdAt = new Date();

console.log(`✅ Cover letter cloned with ID: ${cloned.id}`);
return cloned;
}

addParagraph(content) {
this.paragraphs.push(content);
console.log(`📝 Added paragraph (${content.substring(0, 30)}...)`);
}

setRecipient(company, position) {
this.recipientCompany = company;
this.position = position;
console.log(`🏢 Set recipient: ${position} at ${company}`);
}

display() {
console.log(`📄 Cover Letter Details:`);
console.log(` ID: ${this.id}`);
console.log(` Company: ${this.recipientCompany}`);
console.log(` Position: ${this.position}`);
console.log(` Template: ${this.template}`);
console.log(` Paragraphs: ${this.paragraphs.length}`);
console.log(` Created: ${this.createdAt.toLocaleDateString()}`);
}
}

// Usage
console.log('=== Document Prototype Demo ===\n');

console.log('Creating master resume template:');
console.log('-'.repeat(35));

const masterResume = new Resume();
masterResume.setName('John Doe');
masterResume.addExperience('Tech Corp', 'Senior Developer', 3);
masterResume.addExperience('StartupXYZ', 'Full Stack Developer', 2);
masterResume.addSkill('JavaScript');
masterResume.addSkill('React');
masterResume.addSkill('Node.js');

masterResume.display();

console.log('\n' + '='.repeat(50) + '\n');

console.log('Cloning resume for different applications:');
console.log('-'.repeat(45));

// Clone for frontend position
const frontendResume = masterResume.clone();
frontendResume.setName('John Doe - Frontend Specialist');
frontendResume.addSkill('Vue.js');
frontendResume.addSkill('CSS/SASS');

console.log();
frontendResume.display();

console.log('\n' + '-'.repeat(30) + '\n');

// Clone for backend position
const backendResume = masterResume.clone();
backendResume.setName('John Doe - Backend Engineer');
backendResume.addExperience('DataCorp', 'Backend Specialist', 1);
backendResume.addSkill('Python');
backendResume.addSkill('PostgreSQL');

console.log();
backendResume.display();

console.log('\n' + '='.repeat(50) + '\n');

console.log('Creating and cloning cover letters:');
console.log('-'.repeat(35));

const masterCoverLetter = new CoverLetter('TechCompany', 'Software Engineer', 'modern');
masterCoverLetter.addParagraph(
'I am writing to express my strong interest in the Software Engineer position...',
);
masterCoverLetter.addParagraph('With over 5 years of experience in software development...');
masterCoverLetter.addParagraph('I am particularly excited about this opportunity because...');

masterCoverLetter.display();

console.log('\n' + '-'.repeat(30) + '\n');

// Clone for different companies
const googleCoverLetter = masterCoverLetter.clone();
googleCoverLetter.setRecipient('Google', 'Senior Software Engineer');

console.log();
googleCoverLetter.display();

🌟 Real-World Example

Game Character System

// Abstract prototype for game characters
class GameCharacter {
constructor() {
if (this.constructor === GameCharacter) {
throw new Error('GameCharacter is abstract and cannot be instantiated');
}
}

clone() {
throw new Error('clone() method must be implemented');
}

display() {
throw new Error('display() method must be implemented');
}
}

// Warrior character prototype
class Warrior extends GameCharacter {
constructor(name = 'Unnamed Warrior', level = 1) {
super();
this.type = 'Warrior';
this.name = name;
this.level = level;
this.health = 100 + level * 10;
this.strength = 15 + level * 2;
this.defense = 12 + level * 1.5;
this.weapons = ['Iron Sword'];
this.armor = ['Leather Armor'];
this.skills = ['Sword Strike', 'Shield Block'];
this.experience = level * 100;
this.id = Math.random().toString(36).substr(2, 9);
}

clone() {
console.log(`⚔️ Cloning warrior: ${this.name} (Level ${this.level})`);

const cloned = new Warrior(this.name, this.level);

// Deep copy arrays and objects
cloned.weapons = [...this.weapons];
cloned.armor = [...this.armor];
cloned.skills = [...this.skills];
cloned.health = this.health;
cloned.strength = this.strength;
cloned.defense = this.defense;
cloned.experience = this.experience;

// Generate new ID
cloned.id = Math.random().toString(36).substr(2, 9);

console.log(`✅ Warrior cloned with ID: ${cloned.id}`);
return cloned;
}

levelUp() {
this.level++;
this.health += 10;
this.strength += 2;
this.defense += 1.5;
this.experience += 100;
console.log(`📈 ${this.name} leveled up to ${this.level}!`);
}

addWeapon(weapon) {
this.weapons.push(weapon);
console.log(`⚔️ ${this.name} acquired: ${weapon}`);
}

addSkill(skill) {
if (!this.skills.includes(skill)) {
this.skills.push(skill);
console.log(`🎯 ${this.name} learned: ${skill}`);
}
}

display() {
console.log(`⚔️ Warrior Character:`);
console.log(` ID: ${this.id}`);
console.log(` Name: ${this.name}`);
console.log(` Level: ${this.level}`);
console.log(` Health: ${this.health}`);
console.log(` Strength: ${this.strength}`);
console.log(` Defense: ${this.defense}`);
console.log(` Weapons: ${this.weapons.join(', ')}`);
console.log(` Armor: ${this.armor.join(', ')}`);
console.log(` Skills: ${this.skills.join(', ')}`);
console.log(` Experience: ${this.experience}`);
}
}

// Mage character prototype
class Mage extends GameCharacter {
constructor(name = 'Unnamed Mage', level = 1) {
super();
this.type = 'Mage';
this.name = name;
this.level = level;
this.health = 70 + level * 7;
this.mana = 100 + level * 15;
this.intelligence = 18 + level * 3;
this.defense = 8 + level * 1;
this.spells = ['Magic Missile', 'Heal'];
this.staff = 'Wooden Staff';
this.robes = ['Apprentice Robes'];
this.spellbooks = ['Basic Magic'];
this.experience = level * 100;
this.id = Math.random().toString(36).substr(2, 9);
}

clone() {
console.log(`🔮 Cloning mage: ${this.name} (Level ${this.level})`);

const cloned = new Mage(this.name, this.level);

// Deep copy arrays and properties
cloned.spells = [...this.spells];
cloned.robes = [...this.robes];
cloned.spellbooks = [...this.spellbooks];
cloned.health = this.health;
cloned.mana = this.mana;
cloned.intelligence = this.intelligence;
cloned.defense = this.defense;
cloned.staff = this.staff;
cloned.experience = this.experience;

// Generate new ID
cloned.id = Math.random().toString(36).substr(2, 9);

console.log(`✅ Mage cloned with ID: ${cloned.id}`);
return cloned;
}

levelUp() {
this.level++;
this.health += 7;
this.mana += 15;
this.intelligence += 3;
this.defense += 1;
this.experience += 100;
console.log(`📈 ${this.name} leveled up to ${this.level}!`);
}

learnSpell(spell) {
if (!this.spells.includes(spell)) {
this.spells.push(spell);
console.log(`${this.name} learned spell: ${spell}`);
}
}

acquireSpellbook(book) {
this.spellbooks.push(book);
console.log(`📚 ${this.name} acquired spellbook: ${book}`);
}

display() {
console.log(`🔮 Mage Character:`);
console.log(` ID: ${this.id}`);
console.log(` Name: ${this.name}`);
console.log(` Level: ${this.level}`);
console.log(` Health: ${this.health}`);
console.log(` Mana: ${this.mana}`);
console.log(` Intelligence: ${this.intelligence}`);
console.log(` Defense: ${this.defense}`);
console.log(` Staff: ${this.staff}`);
console.log(` Spells: ${this.spells.join(', ')}`);
console.log(` Robes: ${this.robes.join(', ')}`);
console.log(` Spellbooks: ${this.spellbooks.join(', ')}`);
console.log(` Experience: ${this.experience}`);
}
}

// Character factory using prototypes
class CharacterFactory {
constructor() {
this.prototypes = new Map();
}

registerPrototype(key, prototype) {
this.prototypes.set(key, prototype);
console.log(`📝 Registered prototype: ${key} (${prototype.type})`);
}

createCharacter(key, customizations = {}) {
if (!this.prototypes.has(key)) {
throw new Error(`Prototype '${key}' not found`);
}

console.log(`🏭 Creating character from prototype: ${key}`);
const prototype = this.prototypes.get(key);
const character = prototype.clone();

// Apply customizations
Object.keys(customizations).forEach((prop) => {
if (character.hasOwnProperty(prop)) {
character[prop] = customizations[prop];
console.log(`🎨 Customized ${prop}: ${customizations[prop]}`);
}
});

return character;
}

listPrototypes() {
console.log('📋 Available Prototypes:');
this.prototypes.forEach((prototype, key) => {
console.log(` ${key}: ${prototype.type} (Level ${prototype.level})`);
});
}
}

// Usage
console.log('\n=== Game Character Prototype Demo ===\n');

// Create prototype characters
console.log('Creating prototype characters:');
console.log('-'.repeat(30));

const warriorPrototype = new Warrior('Template Warrior', 5);
warriorPrototype.addWeapon('Steel Sword');
warriorPrototype.addWeapon('Battle Axe');
warriorPrototype.addSkill('Berserker Rage');
warriorPrototype.levelUp();

const magePrototype = new Mage('Template Mage', 4);
magePrototype.learnSpell('Fireball');
magePrototype.learnSpell('Lightning Bolt');
magePrototype.acquireSpellbook('Advanced Elementals');
magePrototype.levelUp();

console.log('\n' + '='.repeat(50) + '\n');

// Setup character factory
console.log('Setting up character factory:');
console.log('-'.repeat(30));

const factory = new CharacterFactory();
factory.registerPrototype('elite-warrior', warriorPrototype);
factory.registerPrototype('elite-mage', magePrototype);

factory.listPrototypes();

console.log('\n' + '='.repeat(50) + '\n');

console.log('Creating characters from prototypes:');
console.log('-'.repeat(40));

// Create customized characters from prototypes
const playerWarrior = factory.createCharacter('elite-warrior', {
name: 'Sir Lancelot',
level: 8,
});

console.log();
playerWarrior.display();

console.log('\n' + '-'.repeat(30) + '\n');

const playerMage = factory.createCharacter('elite-mage', {
name: 'Gandalf',
level: 10,
staff: 'Staff of Power',
});

console.log();
playerMage.display();

console.log('\n' + '-'.repeat(30) + '\n');

// Create multiple NPCs quickly
console.log('Creating NPC army:');
console.log('-'.repeat(18));

const npcWarriors = [];
for (let i = 1; i <= 3; i++) {
const npc = factory.createCharacter('elite-warrior', {
name: `Guard ${i}`,
level: 3 + i,
});
npcWarriors.push(npc);
}

npcWarriors.forEach((npc) => {
console.log(`🛡️ ${npc.name} - Level ${npc.level} - Health: ${npc.health}`);
});

🔧 Another Simple Example

Configuration Templates

// Prototype for application configurations
class AppConfig {
constructor() {
this.database = {
host: 'localhost',
port: 5432,
name: 'myapp',
ssl: false,
};
this.server = {
port: 3000,
host: 'localhost',
cors: true,
};
this.logging = {
level: 'info',
file: './logs/app.log',
console: true,
};
this.cache = {
enabled: false,
ttl: 3600,
provider: 'redis',
};
this.features = [];
this.environment = 'development';
this.created = new Date();
}

clone() {
console.log(`⚙️ Cloning configuration for: ${this.environment}`);

const cloned = new AppConfig();

// Deep copy all configuration objects
cloned.database = { ...this.database };
cloned.server = { ...this.server };
cloned.logging = { ...this.logging };
cloned.cache = { ...this.cache };
cloned.features = [...this.features];
cloned.environment = this.environment;
cloned.created = new Date();

console.log(`✅ Configuration cloned for: ${cloned.environment}`);
return cloned;
}

setEnvironment(env) {
this.environment = env;
console.log(`🌍 Environment set to: ${env}`);
}

enableFeature(feature) {
if (!this.features.includes(feature)) {
this.features.push(feature);
console.log(`🎯 Feature enabled: ${feature}`);
}
}

updateDatabase(options) {
this.database = { ...this.database, ...options };
console.log(`💾 Database config updated:`, options);
}

display() {
console.log(`⚙️ App Configuration [${this.environment.toUpperCase()}]:`);
console.log(` Database: ${this.database.host}:${this.database.port}/${this.database.name}`);
console.log(` Server: ${this.server.host}:${this.server.port}`);
console.log(` Logging: ${this.logging.level}${this.logging.file}`);
console.log(
` Cache: ${this.cache.enabled ? 'Enabled' : 'Disabled'} (${this.cache.provider})`,
);
console.log(` Features: ${this.features.join(', ') || 'None'}`);
console.log(` Created: ${this.created.toLocaleString()}`);
}
}

// Usage
console.log('\n=== Configuration Prototype Demo ===\n');

console.log('Creating base configuration:');
console.log('-'.repeat(30));

const baseConfig = new AppConfig();
baseConfig.enableFeature('user-authentication');
baseConfig.enableFeature('email-notifications');
baseConfig.display();

console.log('\n' + '='.repeat(50) + '\n');

console.log('Creating environment-specific configs:');
console.log('-'.repeat(40));

// Development config
const devConfig = baseConfig.clone();
devConfig.setEnvironment('development');
devConfig.updateDatabase({ host: 'localhost', ssl: false });
devConfig.enableFeature('debug-mode');
devConfig.enableFeature('hot-reload');

console.log();
devConfig.display();

console.log('\n' + '-'.repeat(30) + '\n');

// Production config
const prodConfig = baseConfig.clone();
prodConfig.setEnvironment('production');
prodConfig.updateDatabase({
host: 'prod-db.company.com',
ssl: true,
port: 5433,
});
prodConfig.server.port = 8080;
prodConfig.server.host = '0.0.0.0';
prodConfig.logging.level = 'error';
prodConfig.cache.enabled = true;

console.log();
prodConfig.display();

console.log('\n' + '-'.repeat(30) + '\n');

// Staging config
const stagingConfig = prodConfig.clone();
stagingConfig.setEnvironment('staging');
stagingConfig.updateDatabase({ host: 'staging-db.company.com' });
stagingConfig.logging.level = 'warn';
stagingConfig.enableFeature('performance-monitoring');

console.log();
stagingConfig.display();

✅ Pros

  • Performance: Avoids expensive object creation operations
  • Flexibility: Can create objects dynamically at runtime
  • Reduced Subclassing: Alternative to creating many subclasses
  • Configuration: Easy to create pre-configured object templates
  • Independence: Cloned objects are independent of their prototypes

❌ Cons

  • Deep Copying Complexity: Implementing deep cloning can be complex
  • Circular References: Issues with objects that reference each other
  • Memory Usage: May keep references to large prototype objects
  • Clone Method Maintenance: Must maintain clone methods in all classes

🎯 When to Use

  • Expensive Object Creation: When creating objects is costly (database connections, file operations)
  • Complex Configuration: Objects require extensive setup or configuration
  • Runtime Object Creation: Object types are determined at runtime
  • Template Objects: Need to create many similar objects with slight variations
  • Avoiding Subclassing: Alternative to creating many subclasses for similar objects

🔄 Types of Cloning

1. Shallow Copy

  • Copies object properties but not nested objects
  • Faster but shares references to nested objects

2. Deep Copy

  • Recursively copies all nested objects
  • Slower but creates completely independent objects
// Shallow copy example
shallowClone() {
return Object.assign({}, this);
}

// Deep copy example (for simple objects)
deepClone() {
return JSON.parse(JSON.stringify(this));
}

// Custom deep copy with proper handling
customDeepClone() {
const cloned = new this.constructor();
// Copy primitive properties
Object.keys(this).forEach(key => {
if (typeof this[key] !== 'object' || this[key] === null) {
cloned[key] = this[key];
} else if (Array.isArray(this[key])) {
cloned[key] = [...this[key]];
} else {
cloned[key] = { ...this[key] };
}
});
return cloned;
}
  • Factory Method: Both create objects, but Prototype clones existing instances while Factory Method creates new instances
  • Abstract Factory: Can use Prototype to store and clone product prototypes
  • Singleton: Opposite approach - Prototype creates many instances while Singleton ensures only one
  • Builder: Both handle complex object creation, but Builder constructs step-by-step while Prototype clones existing objects

📚 Further Reading