# 212. The Prototype Design Pattern

**🔹 The Prototype Design Pattern in Python**

The **Prototype Pattern** is a **creational design pattern** that allows us to **clone** objects without depending on their classes. Instead of creating a new object from scratch, we copy an existing one.

Here are **10 Python snippets** demonstrating **object cloning using the Prototype Pattern**.

***

#### **🔹 1. Basic Prototype Pattern**

```python
import copy

class Prototype:
    def clone(self):
        return copy.deepcopy(self)

class Person(Prototype):
    def __init__(self, name, age):
        self.name = name
        self.age = age

p1 = Person("Alice", 25)
p2 = p1.clone()

print(p1.name, p1.age)  # Alice 25
print(p2.name, p2.age)  # Alice 25
print(p1 is p2)         # False (Different objects)
```

🔍 **Issue:**

* The cloned object (`p2`) is a **separate** instance from `p1`, but it has the same attributes.

***

#### **🔹 2. Cloning a List in an Object**

```python
class Car(Prototype):
    def __init__(self, model, features):
        self.model = model
        self.features = features  # List

car1 = Car("Tesla Model S", ["Autopilot", "Self-driving"])
car2 = car1.clone()

car2.features.append("Battery Upgrade")
print(car1.features)  # ['Autopilot', 'Self-driving', 'Battery Upgrade'] (Oops!)
```

🔍 **Issue:**

* Cloning with `copy.copy()` (shallow copy) shares the **same list reference**.
* **Solution:** Use `copy.deepcopy()` instead.

***

#### **🔹 3. Deep Copy to Avoid Shared Mutable Objects**

```python
class Car(Prototype):
    def __init__(self, model, features):
        self.model = model
        self.features = features  # List

car1 = Car("Tesla Model S", ["Autopilot", "Self-driving"])
car2 = car1.clone()

car2.features.append("Battery Upgrade")

print(car1.features)  # ['Autopilot', 'Self-driving']
print(car2.features)  # ['Autopilot', 'Self-driving', 'Battery Upgrade']
```

🔍 **Fix:**

* `copy.deepcopy()` ensures `car1.features` and `car2.features` are separate.

***

#### **🔹 4. Cloning Objects with Nested Structures**

```python
class Engine:
    def __init__(self, horsepower):
        self.horsepower = horsepower

class Car(Prototype):
    def __init__(self, model, engine):
        self.model = model
        self.engine = engine

car1 = Car("BMW M5", Engine(600))
car2 = car1.clone()

car2.engine.horsepower = 700

print(car1.engine.horsepower)  # 600 (Original remains unchanged)
print(car2.engine.horsepower)  # 700 (Cloned object modified)
```

🔍 **Benefit:**

* Deep copying ensures that modifications to `car2` **do not affect** `car1`.

***

#### **🔹 5. Storing Prototypes in a Registry**

```python
class PrototypeRegistry:
    def __init__(self):
        self._registry = {}

    def register(self, name, obj):
        self._registry[name] = obj

    def clone(self, name):
        return self._registry[name].clone()

registry = PrototypeRegistry()
registry.register("car", Car("Audi A6", ["Sunroof", "Leather Seats"]))

car_clone = registry.clone("car")
print(car_clone.model)  # Audi A6
print(car_clone.features)  # ['Sunroof', 'Leather Seats']
```

🔍 **Use Case:**

* A **prototype registry** lets us create clones dynamically.

***

#### **🔹 6. Prototype with a Factory Method**

```python
class PrototypeFactory:
    prototypes = {}

    @staticmethod
    def register(name, obj):
        PrototypeFactory.prototypes[name] = obj

    @staticmethod
    def clone(name):
        return PrototypeFactory.prototypes[name].clone()

class Laptop(Prototype):
    def __init__(self, brand, specs):
        self.brand = brand
        self.specs = specs

PrototypeFactory.register("gaming", Laptop("Alienware", {"RAM": "16GB", "GPU": "RTX 3080"}))
gaming_laptop = PrototypeFactory.clone("gaming")

print(gaming_laptop.brand)  # Alienware
print(gaming_laptop.specs)  # {'RAM': '16GB', 'GPU': 'RTX 3080'}
```

🔍 **Benefit:**

* Factory methods allow structured prototype creation.

***

#### **🔹 7. Cloning Objects with Custom Adjustments**

```python
class Person(Prototype):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def clone_with_changes(self, **kwargs):
        obj = self.clone()
        obj.__dict__.update(kwargs)
        return obj

p1 = Person("John", 30)
p2 = p1.clone_with_changes(age=35)  # Clone and update age

print(p1.age)  # 30
print(p2.age)  # 35
```

🔍 **Benefit:**

* Allows cloning **with modifications**.

***

#### **🔹 8. Preventing Cloning (Singleton-Like Behavior)**

```python
class SingletonPrototype:
    _instance = None

    def clone(self):
        raise RuntimeError("Cloning not allowed for singleton objects")

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

singleton = SingletonPrototype.get_instance()
# singleton.clone()  # ❌ RuntimeError: Cloning not allowed
```

🔍 **Use Case:**

* Ensures **singleton objects** cannot be cloned.

***

#### **🔹 9. Prototype with Class-Level Attributes**

```python
class Configuration(Prototype):
    settings = {"theme": "dark", "language": "English"}

    def __init__(self):
        self.settings = self.__class__.settings.copy()  # Avoid shared reference

config1 = Configuration()
config2 = config1.clone()
config2.settings["theme"] = "light"

print(config1.settings["theme"])  # dark (unchanged)
print(config2.settings["theme"])  # light (modified)
```

🔍 **Issue:**

* **Without `.copy()`, both objects share the same `settings` reference!**

***

#### **🔹 10. Thread-Safe Cloning in a Multi-Threaded Environment**

```python
import threading

class ThreadSafePrototype(Prototype):
    def __init__(self, data):
        self.lock = threading.Lock()
        self.data = data

    def safe_clone(self):
        with self.lock:
            return self.clone()

proto = ThreadSafePrototype({"counter": 1})

def modify_clone():
    obj = proto.safe_clone()
    obj.data["counter"] += 1
    print(obj.data["counter"])

threads = [threading.Thread(target=modify_clone) for _ in range(5)]
for t in threads:
    t.start()
for t in threads:
    t.join()
```

🔍 **Use Case:**

* Avoids race conditions when **cloning in multi-threaded applications**.

***

**🚀 Summary**

| **Concept**                 | **Description**                                       |
| --------------------------- | ----------------------------------------------------- |
| **Basic Prototype**         | Clones an object using `copy.deepcopy()`              |
| **Avoid Shared References** | Use deep copy to prevent shared mutable objects       |
| **Prototype Registry**      | Store prototypes and retrieve clones dynamically      |
| **Factory Pattern**         | Combine Prototype with Factory for structured cloning |
| **Custom Adjustments**      | Clone while modifying attributes dynamically          |
| **Prevent Cloning**         | Restrict cloning in singleton objects                 |
| **Thread-Safe Cloning**     | Use locks to avoid race conditions                    |

***

**🚀 Best Practices**

✅ Use **deep copy** to avoid reference-sharing issues.\
✅ Store prototypes in a **registry** for structured access.\
✅ Implement **clone\_with\_changes()** for dynamic modifications.\
✅ Ensure **thread safety** if cloning in a multi-threaded environment.
