Skip to content

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.

4 min read
CP4 - Prototype - Learn Design Pattern From Simple Things

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

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:

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:

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
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:

# nothing here

Source Code: Correct Implement

Related posts

You found a tiny easter egg. Keep poking around!