Prototype Pattern

Creational

What is it?

Creates objects based on a template of an existing object through cloning. JavaScript's prototypal inheritance makes this pattern very natural.

Why use it?

The Prototype pattern is used to create new objects by copying an existing object, known as the prototype. This is useful when the cost of creating an object is more expensive or complex than copying an existing one. It leverages JavaScript's prototypal inheritance and allows for creating objects dynamically.

Code Example

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Prototype Pattern Example
class ShapePrototype {
  constructor() {
    this.x = 0;
    this.y = 0;
    this.color = '#000000';
  }
  
  clone() {
    // Create a new instance with the same prototype
    const cloned = Object.create(Object.getPrototypeOf(this));
    
    // Copy all properties
    for (let prop in this) {
      if (this.hasOwnProperty(prop)) {
        // Deep clone objects and arrays
        if (typeof this[prop] === 'object' && this[prop] !== null) {
          cloned[prop] = Array.isArray(this[prop]) 
            ? [...this[prop]] 
            : { ...this[prop] };
        } else {
          cloned[prop] = this[prop];
        }
      }
    }
    
    return cloned;
  }
  
  move(x, y) {
    this.x = x;
    this.y = y;
  }
  
  setColor(color) {
    this.color = color;
  }
}

class Rectangle extends ShapePrototype {
  constructor(width = 0, height = 0) {
    super();
    this.width = width;
    this.height = height;
    this.type = 'rectangle';
  }
  
  getArea() {
    return this.width * this.height;
  }
}

class Circle extends ShapePrototype {
  constructor(radius = 0) {
    super();
    this.radius = radius;
    this.type = 'circle';
  }
  
  getArea() {
    return Math.PI * this.radius * this.radius;
  }
}

// Shape Registry for managing prototypes
class ShapeRegistry {
  constructor() {
    this.shapes = {};
  }
  
  registerShape(name, shape) {
    this.shapes[name] = shape;
  }
  
  createShape(name) {
    const prototype = this.shapes[name];
    if (!prototype) {
      throw new Error(`Shape '${name}' not found in registry`);
    }
    return prototype.clone();
  }
}

// Usage
const registry = new ShapeRegistry();

// Register prototype shapes
const rectanglePrototype = new Rectangle(100, 50);
rectanglePrototype.setColor('#FF0000');
registry.registerShape('red-rectangle', rectanglePrototype);

const circlePrototype = new Circle(30);
circlePrototype.setColor('#0000FF');
registry.registerShape('blue-circle', circlePrototype);

// Create new shapes from prototypes
const rect1 = registry.createShape('red-rectangle');
rect1.move(10, 20);

const rect2 = registry.createShape('red-rectangle');
rect2.move(100, 200);
rect2.setColor('#00FF00'); // Change color of this instance

const circle1 = registry.createShape('blue-circle');
circle1.move(50, 50);

console.log(rect1); // Rectangle at(10, 20) with red color
console.log(rect2); // Rectangle at(100, 200) with green color
console.log(circle1); // Circle at(50, 50) with blue color

// Native JavaScript prototype example
const carPrototype = {
  wheels: 4,
  start() {
    console.log('Engine started');
  },
  clone() {
    return Object.create(this);
  }
};

const car1 = carPrototype.clone();
car1.brand = 'Toyota';
car1.model = 'Camry';

const car2 = carPrototype.clone();
car2.brand = 'Honda';
car2.model = 'Accord';

console.log(car1.wheels); // 4 (inherited from prototype)
console.log(car2.wheels); // 4 (inherited from prototype)

Quick Facts

Category
Creational
Common Use Cases
Object creation, instance management

Other Creational Patterns