Prototype

Prototype Pattern

Introduction

The Prototype pattern is a creational design pattern that involves creating new objects by copying an existing object, known as the prototype. This pattern is particularly useful when the cost of creating an object is more expensive or complex than copying an existing one.

Purpose

  • To create new objects by duplicating an existing object, which serves as a prototype.
  • To reduce the need for subclassing by cloning objects.

Use Cases

  • When the classes to instantiate are specified at runtime.
  • To avoid building a class hierarchy of factories that parallels the class hierarchy of products.
  • When instances of a class can have only a few different combinations of state.

Advantages/Disadvantages

Advantages:

  • Adds and removes products at runtime.
  • Specifies new objects by varying values.
  • Reduces the need for creating subclasses.

Disadvantages:

  • Each subclass of Prototype must implement the cloning method.
  • Cloning complex objects that have circular references might be very tricky.

Implementation in JavaScript

javascript
	class Prototype {
	  clone() {
	    throw new Error('You must implement the clone method');
	  }
	}
	
	class ConcretePrototype extends Prototype {
	  constructor(field) {
	    super();
	    this.field = field;
	  }
	
	  clone() {
	    return new ConcretePrototype(this.field);
	  }
	}
	
	// Usage
	const original = new ConcretePrototype('value');
	const clone = original.clone();
	
	console.log(original.field); // "value"
	console.log(clone.field); // "value", cloned from original

Practical Example

Suppose we have a logging system that needs different types of loggers (e.g., for errors, warnings, info). These loggers share common properties but differ slightly. We can use the Prototype pattern for easy duplication and customization.

javascript
	class LoggerPrototype {
	  constructor(type, level) {
	    this.type = type;
	    this.level = level;
	  }
	
	  clone() {
	    return new LoggerPrototype(this.type, this.level);
	  }
	
	  log(message) {
	    console.log(`[${this.type} - ${this.level}]: ${message}`);
	  }
	}
	
	// Usage
	const errorLoggerPrototype = new LoggerPrototype('Error', 'High');
	const warningLoggerPrototype = new LoggerPrototype('Warning', 'Medium');
	
	// Cloning prototypes for specific situations
	const systemErrorLogger = errorLoggerPrototype.clone();
	const fileWarningLogger = warningLoggerPrototype.clone();
	
	systemErrorLogger.log('System failure!'); // [Error - High]: System failure!
	fileWarningLogger.log('File not found.'); // [Warning - Medium]: File not found.

In this practical example, LoggerPrototype is used to create different logger prototypes, which are then cloned to create specific loggers for various situations. This approach simplifies the creation of objects that have similar configurations but different data.

Next, we will discuss the Adapter pattern.