Skip to content

Prototype Design Pattern

Overview

...Refer to Book or Videos for extra content.

Terminology

...Refer to Book or Videos for extra content.

Prototype UML Diagram

Prototype UML Diagram

Source Code

...Refer to Book or Videos for extra content.

./src/prototype/prototype-concept.ts

interface IProtoType {
    // interface with clone method
    clone(): this
    // The clone is deep or shallow.
    // It is up to you how you want to implement
    // the details in your concrete class
}

class MyClass implements IProtoType {
    // A Concrete Class
    field: number[]

    constructor(field: number[]) {
        this.field = field // any value of any type
    }

    clone() {
        return Object.assign({}, this) // shallow copy
        // return JSON.parse(JSON.stringify(this)); //deep copy
    }
}

// The Client
// Create an object containing an array
const OBJECT1 = new MyClass([1, 2, 3, 4])
console.log(`OBJECT1: ${JSON.stringify(OBJECT1)}`)

const OBJECT2 = OBJECT1.clone() // Clone
console.log(`OBJECT2: ${JSON.stringify(OBJECT2)}`)
// Change the value of one of the array elements in OBJECT2
// Depending on your clone method, either a shallow or deep copy
// was performed
OBJECT2.field[1] = 101

// Comparing OBJECT1 and OBJECT2
console.log(`OBJECT2: ${JSON.stringify(OBJECT2)}`)
console.log(`OBJECT1: ${JSON.stringify(OBJECT1)}`)

Output

When using the shallow copy approach. Changing the inner item of OBJECT2s array, also affected OBJECT1s array.

node ./dist/prototype/prototype-concept.js
OBJECT1: {"field":[1,2,3,4]}
OBJECT2: {"field":[1,2,3,4]}
OBJECT2: {"field":[1,101,3,4]}
OBJECT1: {"field":[1,101,3,4]}

When using the deep copy approach. Changing the inner item of OBJECT2s array, does not affect OBJECT1s array.

node ./dist/prototype/prototype-concept.js
OBJECT1: {"field":[1,2,3,4]}
OBJECT2: {"field":[1,2,3,4]}
OBJECT2: {"field":[1,101,3,4]}
OBJECT1: {"field":[1,2,3,4]}

Prototype Use Case

...Refer to Book or Videos for extra content.

Example UML Diagram

Prototype Use Case Diagram

Source Code

./src/prototype/client.ts

// Prototype Use Case Example Code
import Document from './document'

// Creating a document containing an array of two arrays
const ORIGINAL_DOCUMENT = new Document('Original', [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
])
console.log(ORIGINAL_DOCUMENT)
console.log()

const DOCUMENT_COPY_1 = ORIGINAL_DOCUMENT.clone(1) // shallow copy
DOCUMENT_COPY_1.name = 'Copy 1'
// This also modified ORIGINAL_DOCUMENT because of the shallow copy
// when using mode 1
DOCUMENT_COPY_1.array[1][2] = 200
console.log(DOCUMENT_COPY_1)
console.log(ORIGINAL_DOCUMENT)
console.log()

const DOCUMENT_COPY_2 = ORIGINAL_DOCUMENT.clone(1) // shallow copy
DOCUMENT_COPY_2.name = 'Copy 2'
// This does NOT modify ORIGINAL_DOCUMENT because it changes the
// complete array[1] reference that was shallow copied when using mode 1
DOCUMENT_COPY_2.array[1] = [9, 10, 11, 12]
console.log(DOCUMENT_COPY_2)
console.log(ORIGINAL_DOCUMENT)
console.log()

const DOCUMENT_COPY_3 = ORIGINAL_DOCUMENT.clone(2) // deep copy
DOCUMENT_COPY_3.name = 'Copy 3'
// This does modify ORIGINAL_DOCUMENT because it changes the element of
// array[1][0] that was deep copied recursively when using mode 2
DOCUMENT_COPY_3.array[1][0] = 1234
console.log(DOCUMENT_COPY_3)
console.log(ORIGINAL_DOCUMENT)
console.log()

./src/prototype/document.ts

// A sample document to be used in the Prototype example
import ProtoType from './iprototype'

export default class Document implements ProtoType {
    name: string
    array: [number[], number[]]

    constructor(name: string, array: [number[], number[]]) {
        this.name = name
        this.array = array
    }

    clone(mode: number): Document {
        // This clone method uses different copy techniques
        let array
        if (mode === 2) {
            // results in a deep copy of the Document
            array = JSON.parse(JSON.stringify(this.array))
        } else {
            // default, results in a shallow copy of the Document
            array = Object.assign([], this.array)
        }
        return new Document(this.name, array)
    }
}

./src/prototype/iprototype.ts

// Prototype Concept Sample Code

import Document from './document'

export default interface IProtoType {
    // interface with clone method
    clone(mode: number): Document
    // The clone, deep or shallow.
    // It is up to you how you  want to implement
    // the details in your concrete class"""
}

Output

node ./dist/prototype/client.js
Document {
  name: 'Original',
  array: [ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ] ]
}

Document {
  name: 'Copy 1',
  array: [ [ 1, 2, 3, 4 ], [ 5, 6, 200, 8 ] ]
}
Document {
  name: 'Original',
  array: [ [ 1, 2, 3, 4 ], [ 5, 6, 200, 8 ] ]
}

Document {
  name: 'Copy 2',
  array: [ [ 1, 2, 3, 4 ], [ 9, 10, 11, 12 ] ]
}
Document {
  name: 'Original',
  array: [ [ 1, 2, 3, 4 ], [ 5, 6, 200, 8 ] ]
}

Document {
  name: 'Copy 3',
  array: [ [ 1, 2, 3, 4 ], [ 1234, 6, 200, 8 ] ]
}
Document {
  name: 'Original',
  array: [ [ 1, 2, 3, 4 ], [ 5, 6, 200, 8 ] ]
}

Summary

...Refer to Book or Videos for extra content.