Prototype Pattern
The Prototype Pattern is a creational design pattern that lets you create new objects by copying an existing object, known as the prototype. This pattern is useful when creating a new object is more expensive than copying an existing one, or when you need to create objects with particular state configurations that might be time-consuming to set up from scratch.
Problem
Imagine you have a complex object that's expensive to create, perhaps because it requires resource-intensive operations, database access, or network requests. You need multiple instances of this object, but each might need slight variations. Creating each instance from scratch would be inefficient.
Alternatively, consider a scenario where you have an object with a particular state that would be tedious to recreate manually, and you want to create a new object with that same initial state.
Solution
The Prototype pattern delegates the object-copying process to the actual objects being cloned. The pattern declares a common interface for all objects that support cloning. This interface lets you clone an object without coupling your code to the class of that object.
Typically, such an interface contains a single clone()
method, which returns a copy of the current object. The implementation of the clone()
method is very similar in all classes: it creates an object of the current class and carries over all the field values from the current object to the new one.
Components
- Prototype: The interface that declares the cloning method. It's a common interface for all objects that can be cloned.
- Concrete Prototype: A class that implements the cloning interface, providing the actual cloning implementation.
- Client: Creates a new object by asking a prototype to clone itself.
When to Use
- When object creation is costly, and you want to avoid the expense by copying an existing object.
- When your code shouldn't depend on the concrete classes of objects that you need to copy.
- When you want to hide the complexity of creating new instances from the client.
- When you need to create objects with particular state configurations that might be time-consuming to set up manually.
Benefits
- You can clone objects without coupling to their concrete classes.
- You can avoid repeated initialization code in favor of cloning pre-built prototypes.
- You can produce complex objects more conveniently.
- You get an alternative to inheritance when dealing with configuration presets for complex objects.
Real-World Uses
- Object libraries with pre-configured objects that can be copied and modified.
- Creating complex configurations (like a document with text, images, and styles) by cloning existing ones.
- Game frameworks where game entity templates are cloned to create instances.
- Creating identical or similar DOM elements in web applications.
- Generating variations of a base model in CAD/CAM applications.
Implementation Example
Here's an example of the Prototype pattern in JavaScript, demonstrating how different types of shapes can be cloned:
// Prototype interface
class Shape {
constructor() {
this.x = 0;
this.y = 0;
this.color = "black";
}
clone() {
// Default implementation - create a new object and copy properties
const clone = Object.create(Object.getPrototypeOf(this));
const propNames = Object.getOwnPropertyNames(this);
propNames.forEach(name => {
const desc = Object.getOwnPropertyDescriptor(this, name);
Object.defineProperty(clone, name, desc);
});
return clone;
}
}
// Concrete prototype - Circle
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius || 10;
}
calculateArea() {
return Math.PI * this.radius * this.radius;
}
}
// Concrete prototype - Rectangle
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width || 10;
this.height = height || 10;
}
calculateArea() {
return this.width * this.height;
}
}
// Client code
// Create initial prototypes
const circlePrototype = new Circle(5);
circlePrototype.color = "red";
circlePrototype.x = 10;
circlePrototype.y = 10;
const rectanglePrototype = new Rectangle(10, 20);
rectanglePrototype.color = "blue";
rectanglePrototype.x = 20;
rectanglePrototype.y = 30;
// Create clones with modifications
const circle1 = circlePrototype.clone();
circle1.radius = 10;
circle1.y = 15;
const rectangle1 = rectanglePrototype.clone();
rectangle1.width = 25;
rectangle1.color = "green";
console.log("Circle prototype - Area:", circlePrototype.calculateArea());
console.log("Circle clone - Area:", circle1.calculateArea());
console.log("Rectangle prototype - Area:", rectanglePrototype.calculateArea());
console.log("Rectangle clone - Area:", rectangle1.calculateArea());
In this example:
- The
Shape
class serves as the prototype interface with aclone()
method. Circle
andRectangle
are concrete prototypes that inherit fromShape
.- We create prototype instances, and then clone them to create new objects with modified properties.
- This demonstrates how we can create new objects by copying existing ones, rather than creating them from scratch.
Interactive Demo
The demo below illustrates the Prototype pattern in action. It demonstrates cloning pre-configured document objects with different styles and properties, simulating a simple document editor scenario.
Interactive Demo: Document Editor Prototype
This demo shows how the Prototype pattern can be used in a document editor to clone document templates with different styles and content. Select a template, then clone and modify it.