Featured image of post CP4 - Prototype - Learn Design Pattern From Simple Things

CP4 - Prototype - Learn Design Pattern From Simple Things

Having been with you for a long time, your robot has memories that regular manufacturing can't produce, now you want to clone it. Prototype will be your remedy.

Prototype is a creational design pattern: πŸ–€πŸ€πŸ€πŸ€πŸ€

What is Prototype?

Prototype is a technique for making the original object copyable.

Prototype diagram

Why use Prototype?

  • Just to copy an object.
  • You don’t need to learn Prototype in Python, because we already have a built-in module copy to do that.
    • Since the built-in module copy can clone any object in Python, any object can be a Prototype object.
    • The example of override def __deepcopy__ in refactoring.guru is complicated and incorrect. Don’t copy them. You just need to use the copy module as usual.

Question: So… what is the purpose of this blog?

Answer: Just for knowing that Prototype is not necessary to learn in Python, now skip to another pattern.

Prototype

When to use Prototype?

Question: When do I use Prototype?

Answer: When you use copy.copy or copy.deep_copy, you’re already using Prototype.

Input:

  • Having an original object Robot
  • Create the copy copied_robot from original object original_robot:
    • Coping by (shallow)copy:
      • copied_robot.mutable ↔️ original_robot.mutable
    • Coping by deep copy:
      • copied_robot.mutable πŸ›‘οΈ original_robot.mutable

learn more about mutable and immutable

 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
import copy


class MemoryRobot:
    def __init__(self):
        self.who_am_i = None

    def set_memory(self, who_am_i):
        self.who_am_i = who_am_i


class Robot:
    def __init__(self, age, body_parts, memory):
        self.age = age
        self.body_parts = body_parts
        self.memory = memory

    @property
    def id(self):
        # https://www.digitalocean.com/community/tutorials/python-id
        return str(id(self))[-3:]


if __name__ == "__main__":

    def init_robot():
        robot_body_parts = [
            "head",
            {"left hand", "chest", "right hand"},
            ["left foot", "butt", "right foot"],
        ]
        memory_robot = MemoryRobot()
        robot = Robot(18, robot_body_parts, memory_robot)
        memory_robot.set_memory(robot)
        return robot

    def copy_robot(origin_robot, copy_method):
        wing = "wing"
        tail = "tail"
        tab = "    "
        tab_2 = "    " * 2
        tab_3 = "    " * 3
        print(f"\nCoping by {copy_method.__name__}:")
        copied_robot = copy_method(origin_robot)
        copied_robot.body_parts.append(tail)
        is_origin_robot_changed = origin_robot.body_parts[-1] == tail
        print(f"{tab}Question: update copied_robot changes origin_robot?")
        print(f"{tab_2}Answer: {is_origin_robot_changed}")

        origin_robot.body_parts[1].add(wing)
        is_copied_robot_changed = wing in copied_robot.body_parts[1]
        print(f"{tab}Question: update origin_robot changes copied_robot?")
        print(f"{tab_2}Answer: {is_copied_robot_changed}")

        print(f"{tab}How about reference?")
        print(f"{tab_2}origin_robot reference:")
        print(f"{tab_3}{origin_robot.id} is the ID of origin_robot")
        print(
            f"{tab_3}{origin_robot.memory.who_am_i.id} is the ID of origin_robot.memory.who_am_i"
        )
        print(f"{tab_2}copied_robot reference:")
        print(f"{tab_3}{copied_robot.id} is the ID of copied_robot")
        print(
            f"{tab_3}{copied_robot.memory.who_am_i.id} is the ID of copied_robot.memory.who_am_i"
        )
        return copied_robot

    origin_robot_1 = init_robot()
    shallow_copied_robot = copy_robot(origin_robot_1, copy.copy)

    origin_robot_2 = init_robot()
    deep_copied_robot = copy_robot(origin_robot_2, copy.deepcopy)

Expected Output:

 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
Coping by copy:
    Question: update copied_robot changes origin_robot?
        Answer: True
    Question: update origin_robot changes copied_robot?
        Answer: True
    How about reference?
        origin_robot reference:
            100 is the ID of origin_robot
            100 is the ID of origin_robot.memory.who_am_i
        copied_robot reference:
            101 is the ID of copied_robot
            100 is the ID of copied_robot.memory.who_am_i

Coping by deepcopy:
    Question: update copied_robot changes origin_robot?
        Answer: False
    Question: update origin_robot changes copied_robot?
        Answer: False
    How about reference?
        origin_robot reference:
            200 is the ID of origin_robot
            200 is the ID of origin_robot.memory.who_am_i
        copied_robot reference:
            201 is the ID of copied_robot
            201 is the ID of copied_robot.memory.who_am_i

How to implement Prototype?

Refactoring.guru-Prototype implementation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Robot:
    ...

    def __copy__(self):
        body_parts = copy.copy(self.body_parts)
        memory = copy.copy(self.memory)
        copied_robot = self.__class__(self.age, body_parts, memory)
        copied_robot.__dict__.update(self.__dict__)
        return copied_robot

    def __deepcopy__(self, _=None):
        if _ is None:
            _ = {}
        body_parts = copy.deepcopy(self.body_parts, _)
        memory = copy.deepcopy(self.memory, _)
        copied_robot = self.__class__(self.age, body_parts, memory)
        copied_robot.__dict__ = copy.deepcopy(self.__dict__, _)
        return copied_robot
    ...

This implement is incorrect because:

  • in the deep copy:
    • How about reference? (output line #24 != #25)
      • copied_robot ID 401 != copied_robot.memory.who_am_i ID 402
 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
Coping by copy:
    Question: update copied_robot changes origin_robot?
        Answer: True
    Question: update origin_robot changes copied_robot?
        Answer: True
    How about reference?
        origin_robot reference:
            300 is the ID of origin_robot
            300 is the ID of origin_robot.memory.who_am_i
        copied_robot reference:
            301 is the ID of copied_robot
            300 is the ID of copied_robot.memory.who_am_i

Coping by deepcopy:
    Question: update copied_robot changes origin_robot?
        Answer: False
    Question: update origin_robot changes copied_robot?
        Answer: False
    How about reference?
        origin_robot reference:
            400 is the ID of origin_robot
            400 is the ID of origin_robot.memory.who_am_i
        copied_robot reference:
            401 is the ID of copied_robot
            402 is the ID of copied_robot.memory.who_am_i

Source Code: Incorrect Implement

Prototype Implementation:

You’re already using Prototype from Input:

1
# nothing here

Source Code: Correct Implement

Made with the laziness πŸ¦₯
by a busy guy