Skip to content

Prototype Design Pattern

Video Lecture

Section Video Links
Prototype Overview Prototype Overview Prototype Overview Prototype Overview 
Prototype Use Case Prototype Use Case Prototype Use Case Prototype Use Case 
Python id() function python id function python id function python id function 

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.

./prototype/prototype_concept.py

 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
# pylint: disable=too-few-public-methods
"Prototype Concept Sample Code"

from abc import ABCMeta, abstractmethod

class IProtoType(metaclass=ABCMeta):
    "interface with clone method"
    @staticmethod
    @abstractmethod
    def clone():
        """The clone, deep or shallow.
        It is up to you how you want to implement
        the details in your concrete class"""

class MyClass(IProtoType):
    "A Concrete Class"

    def __init__(self, field):
        self.field = field  # any value of any type

    def clone(self):
        " This clone method uses a shallow copy technique "
        return type(self)(
            self.field  # a shallow copy is returned
            # self.field.copy() # this is also a shallow copy, but has
            # also shallow copied the first level of the field. So it
            # is essentially a shallow copy but 2 levels deep. To
            # recursively deep copy collections containing inner
            # collections,
            # eg lists of lists,
            # Use https://docs.python.org/3/library/copy.html instead.
            # See example below.
        )

    def __str__(self):
        return f"{id(self)}\tfield={self.field}\ttype={type(self.field)}"

# The Client
OBJECT1 = MyClass([1, 2, 3, 4])  # Create the object containing a list
print(f"OBJECT1 {OBJECT1}")

OBJECT2 = OBJECT1.clone()  # Clone

# Change the value of one of the list elements in OBJECT2,
# to see if it also modifies the list element in OBJECT1.
# If it changed OBJECT1s copy also, then the clone was done
# using a 1 level shallow copy process.
# Modify the clone method above to try a 2 level shallow copy instead
# and compare the output
OBJECT2.field[1] = 101

# Comparing OBJECT1 and OBJECT2
print(f"OBJECT2 {OBJECT2}")
print(f"OBJECT1 {OBJECT1}")

Output

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

1
2
3
4
python ./prototype/prototype_concept.py
OBJECT1 1808814538656   field=[1, 2, 3, 4]      type=<class 'list'>
OBJECT2 1808814538464   field=[1, 101, 3, 4]    type=<class 'list'>
OBJECT1 1808814538656   field=[1, 101, 3, 4]    type=<class 'list'>

When using the 2-level shallow, or deep copy approach. Changing the inner item of OBJECT2s list, does not affect OBJECT1s list. Read notes below for caveat.

1
2
3
4
python .\prototype\prototype_concept.py
OBJECT1 1808814538656   field=[1, 2, 3, 4]      type=<class 'list'>
OBJECT2 1808814538464   field=[1, 101, 3, 4]    type=<class 'list'>
OBJECT1 1808814538656   field=[1, 2, 3, 4]      type=<class 'list'>

Notes

The 2-level shallow copy was used in the above sample code. This only copies collections (list, dictionary, set) one level deep.

E.g., It won't deep copy collections containing inner collections, such as lists of lists, or dictionaries of lists, sets and tuples of any combination, etc.

For full recursive deep copying, use the library at https://docs.python.org/3/library/copy.html

Example Use Case

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

Example UML Diagram

Prototype Use Case Diagram

Source Code

./prototype/client.py

 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
"Prototype Use Case Example Code"
from document import Document

# Creating a document containing a list of two lists
ORIGINAL_DOCUMENT = Document("Original", [[1, 2, 3, 4], [5, 6, 7, 8]])
print(ORIGINAL_DOCUMENT)
print()

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.list[1][2] = 200
print(DOCUMENT_COPY_1)
print(ORIGINAL_DOCUMENT)
print()

DOCUMENT_COPY_2 = ORIGINAL_DOCUMENT.clone(2)  # 2 level shallow copy
DOCUMENT_COPY_2.name = "Copy 2"
# This does NOT modify ORIGINAL_DOCUMENT because it changes the
# list[1] reference that was deep copied when using mode 2
DOCUMENT_COPY_2.list[1] = [9, 10, 11, 12]
print(DOCUMENT_COPY_2)
print(ORIGINAL_DOCUMENT)
print()

DOCUMENT_COPY_3 = ORIGINAL_DOCUMENT.clone(2)  # 2 level shallow copy
DOCUMENT_COPY_3.name = "Copy 3"
# This does modify ORIGINAL_DOCUMENT because it changes the element of
# list[1][0] that was NOT deep copied recursively when using mode 2
DOCUMENT_COPY_3.list[1][0] = "1234"
print(DOCUMENT_COPY_3)
print(ORIGINAL_DOCUMENT)
print()

DOCUMENT_COPY_4 = ORIGINAL_DOCUMENT.clone(3)  # deep copy (recursive)
DOCUMENT_COPY_4.name = "Copy 4"
# This does NOT modify ORIGINAL_DOCUMENT because it
# deep copies all levels recursively when using mode 3
DOCUMENT_COPY_4.list[1][0] = "5678"
print(DOCUMENT_COPY_4)
print(ORIGINAL_DOCUMENT)
print()

./prototype/document.py

 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
"A sample document to be used in the Prototype example"
import copy  # a python library useful for deep copying
from interface_prototype import IProtoType

class Document(IProtoType):
    "A Concrete Class"

    def __init__(self, name, l):
        self.name = name
        self.list = l

    def clone(self, mode):
        " This clone method uses different copy techniques "
        if mode == 1:
            # results in a 1 level shallow copy of the Document
            doc_list = self.list
        if mode == 2:
            # results in a 2 level shallow copy of the Document
            # since it also create new references for the 1st level list
            # elements aswell
            doc_list = self.list.copy()
        if mode == 3:
            # recursive deep copy. Slower but results in a new copy
            # where no sub elements are shared by reference
            doc_list = copy.deepcopy(self.list)

        return type(self)(
            self.name,  # a shallow copy is returned of the name property
            doc_list  # copy method decided by mode argument
        )

    def __str__(self):
        " Overriding the default __str__ method for our object."
        return f"{id(self)}\tname={self.name}\tlist={self.list}"

./prototype/interface_prototype.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# pylint: disable=too-few-public-methods
"Prototype Concept Sample Code"
from abc import ABCMeta, abstractmethod

class IProtoType(metaclass=ABCMeta):
    "interface with clone method"
    @staticmethod
    @abstractmethod
    def clone(mode):
        """The clone, deep or shallow.
        It is up to you how you  want to implement
        the details in your concrete class"""

Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
python ./prototype/client.py
2520526585808   name=Original   list=[[1, 2, 3, 4], [5, 6, 7, 8]]

2520526585712   name=Copy 1     list=[[1, 2, 3, 4], [5, 6, 200, 8]]
2520526585808   name=Original   list=[[1, 2, 3, 4], [5, 6, 200, 8]]

2520526585664   name=Copy 2     list=[[1, 2, 3, 4], [9, 10, 11, 12]]
2520526585808   name=Original   list=[[1, 2, 3, 4], [5, 6, 200, 8]]

2520526585520   name=Copy 3     list=[[1, 2, 3, 4], ['1234', 6, 200, 8]]
2520526585808   name=Original   list=[[1, 2, 3, 4], ['1234', 6, 200, 8]]

2520526585088   name=Copy 4     list=[[1, 2, 3, 4], ['5678', 6, 200, 8]]
2520526585808   name=Original   list=[[1, 2, 3, 4], ['1234', 6, 200, 8]]

New Coding Concepts

Python id() Function

The Python id() function returns the memory address of an object.

All objects in Python will have a memory address.

You can test if an object is unique in Python by comparing its ID.

In the examples above, I can tell how deep the copies of the dictionaries and lists were, because the IDs of the inner items will be different. I.e., they point to different memory identifiers.

Note that every time you start a Python process, the IDs assigned at runtime will likely be different.

Also note that integers in Python also have their own IDs.

1
2
3
print(id(0))
print(id(1))
print(id(2))

Outputs

1
2
3
2032436013328
2032436013360
2032436013392

Summary

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