Skip to content

Commit af86a14

Browse files
committed
Merge branch 'develop'
2 parents a4adc37 + 4cc113d commit af86a14

2 files changed

Lines changed: 468 additions & 0 deletions

File tree

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
---
2+
title: GoF Design Patterns - Prototype
3+
date: 2026-01-27
4+
category: Coding
5+
description: A practical guide to the Prototype pattern from the GoF collection: concept, when to use it, C++ and Python examples, implementation notes and references.
6+
tags: [Coding, DesignPattern]
7+
recommended: true
8+
thumbnail: assets/img/ogp.png
9+
---
10+
11+
Hello! I'm Pan-kun.
12+
13+
This article covers the Prototype pattern from the GoF (Gang of Four) collection.
14+
It provides practical guidance: conceptual overview, C++/Python sample code, when to use the pattern, and implementation cautions.
15+
16+
## Introduction
17+
18+
The Prototype pattern is a creational design pattern that creates new objects by copying an existing instance (a prototype). It can be useful when object construction is costly or when it's inconvenient to determine the concrete class at creation time.
19+
20+
Here is the definition from Wikipedia:
21+
22+
> "In software engineering, the prototype pattern is a creational design pattern in which new objects are created by copying a prototypical instance."
23+
> (Source: https://en.wikipedia.org/wiki/Prototype_pattern)
24+
25+
The core idea is to use cloning to produce new objects. This is especially effective for objects with expensive initialization or when the concrete type is unknown or variable at runtime.
26+
27+
[!CARD](https://en.wikipedia.org/wiki/Prototype_pattern)
28+
29+
## Structure (Participants)
30+
31+
A typical composition includes:
32+
33+
- `Prototype`: Provides the interface for cloning (e.g., `Clone()`).
34+
- `ConcretePrototype`: Concrete classes that implement cloning and adjust copy semantics (deep vs. shallow).
35+
- `Client`: Obtains new instances by calling `Prototype::Clone()` (or `clone()`).
36+
- `PrototypeRegistry` (optional): Manages prototypes by name/key so clients can request clones at runtime.
37+
38+
A simple class diagram (conceptual):
39+
40+
```mermaid
41+
classDiagram
42+
class Prototype {
43+
<<interface>>
44+
+Clone() Prototype*
45+
}
46+
47+
class ConcretePrototypeA
48+
class ConcretePrototypeB
49+
Prototype <|-- ConcretePrototypeA
50+
Prototype <|-- ConcretePrototypeB
51+
52+
class Client {
53+
+CreateFromPrototype(Prototype*) Prototype*
54+
}
55+
56+
Client ..> Prototype : uses
57+
Client ..> ConcretePrototypeA : clones
58+
Client ..> ConcretePrototypeB : clones
59+
```
60+
61+
## Pros / Cons
62+
63+
Pros
64+
65+
- Avoids costly initialization by cloning an existing instance.
66+
- Allows creating objects without knowing their concrete classes at runtime (polymorphic cloning).
67+
- Can be dynamically reconfigured by swapping prototypes (runtime flexibility).
68+
69+
Cons
70+
71+
- Implementing correct cloning semantics (deep vs. shallow) can be tricky, especially with shared resources.
72+
- Managing object identity (IDs, handles) after cloning requires clear rules.
73+
- If cloning can be easily achieved via serialization/deserialization or simple copy constructors, the pattern may be unnecessary.
74+
75+
## Example: C++ (recommended approach)
76+
77+
In C++ a common practice is to have `virtual std::unique_ptr<Prototype> Clone() const` so that cloning returns clear ownership. Below is a compact learning example.
78+
79+
```cpp
80+
#include <iostream>
81+
#include <memory>
82+
#include <string>
83+
#include <unordered_map>
84+
85+
// Prototype base
86+
class Prototype {
87+
public:
88+
virtual ~Prototype() = default;
89+
// Return a new ownership-bearing clone
90+
virtual std::unique_ptr<Prototype> Clone() const = 0;
91+
virtual void Show() const = 0;
92+
};
93+
94+
// Concrete prototype (example with deep-copy semantics)
95+
class ConcretePrototype : public Prototype {
96+
public:
97+
ConcretePrototype(const std::string& name, int data)
98+
: name_(name), data_(new int(data)) {}
99+
100+
// Copy constructor: deep copy
101+
ConcretePrototype(const ConcretePrototype& other)
102+
: name_(other.name_), data_(new int(*other.data_)) {}
103+
104+
// Clone uses the copy constructor to produce a deep copy
105+
std::unique_ptr<Prototype> Clone() const override {
106+
return std::make_unique<ConcretePrototype>(*this);
107+
}
108+
109+
void Show() const override {
110+
std::cout << "ConcretePrototype name=" << name_ << " data=" << *data_ << "\n";
111+
}
112+
113+
~ConcretePrototype() { delete data_; }
114+
115+
private:
116+
std::string name_;
117+
int* data_; // Simplified: prefer smart pointers in production
118+
};
119+
120+
// Optional: a registry to hold prototype instances
121+
class PrototypeRegistry {
122+
public:
123+
void Register(const std::string& key, std::unique_ptr<Prototype> prototype) {
124+
registry_[key] = std::move(prototype);
125+
}
126+
127+
std::unique_ptr<Prototype> Create(const std::string& key) const {
128+
auto it = registry_.find(key);
129+
if (it != registry_.end()) {
130+
return it->second->Clone();
131+
}
132+
return nullptr;
133+
}
134+
135+
private:
136+
std::unordered_map<std::string, std::unique_ptr<Prototype>> registry_;
137+
};
138+
139+
int main() {
140+
PrototypeRegistry registry;
141+
registry.Register("protoA", std::make_unique<ConcretePrototype>("A", 100));
142+
143+
auto p1 = registry.Create("protoA");
144+
auto p2 = registry.Create("protoA");
145+
146+
if (p1) p1->Show(); // ConcretePrototype name=A data=100
147+
if (p2) p2->Show(); // ConcretePrototype name=A data=100
148+
149+
// p1 and p2 are independent objects if deep copy is implemented correctly
150+
return 0;
151+
}
152+
```
153+
154+
Key points
155+
156+
- `Clone()` should always return a newly owned object (here via `std::unique_ptr`) so the caller controls lifetime.
157+
- Implement deep copy for members that must not be shared (dynamic arrays, raw pointers, external handles).
158+
- Prefer `std::unique_ptr` / `std::shared_ptr` over raw pointers in production code.
159+
160+
## Example: Python (lightweight)
161+
162+
In Python you can use the `copy` module or implement a `clone()` method. This example demonstrates deep copy.
163+
164+
```python
165+
import copy
166+
from abc import ABC, abstractmethod
167+
168+
class Prototype(ABC):
169+
@abstractmethod
170+
def clone(self):
171+
pass
172+
173+
class ConcretePrototype(Prototype):
174+
def __init__(self, name, data):
175+
self.name = name
176+
self.data = data # mutable object assumed
177+
178+
def clone(self):
179+
# choose between shallow and deep copy as needed
180+
# return copy.copy(self) # shallow copy
181+
return copy.deepcopy(self) # deep copy
182+
183+
def show(self):
184+
print(f"ConcretePrototype name={self.name} data={self.data}")
185+
186+
if __name__ == "__main__":
187+
proto = ConcretePrototype("pyA", {"k": [1,2,3]})
188+
c1 = proto.clone()
189+
c2 = proto.clone()
190+
c1.show()
191+
c2.show()
192+
```
193+
194+
You can customize cloning behavior by implementing `__copy__` / `__deepcopy__`.
195+
196+
## Implementation Notes (must-check items)
197+
198+
- Clarify deep vs. shallow copying boundaries. Define which resources should be shared (reference-counted) and which must be duplicated.
199+
- External resources (files, sockets, DB connections) often cannot be trivially copied; provide reinitialization or reconnection logic in clones.
200+
- Object identity: if IDs or handles must be unique, assign new IDs after cloning.
201+
- Thread safety: if prototypes are shared and cloned concurrently, protect registry/state with appropriate synchronization.
202+
- Consider serialization/deserialization as an alternative cloning mechanism when appropriate.
203+
204+
## When to Use / Alternatives
205+
206+
When to use (guidelines)
207+
208+
- When initialization is expensive and you can prepare a prototype once and clone it many times.
209+
- When you need many similar variations derived from a base instance.
210+
- When concrete types are dynamic at runtime and you want to avoid enumerating types in factories.
211+
212+
Alternatives
213+
214+
- If a simple copy constructor suffices, you may not need the full pattern.
215+
- Use `Factory Method` / `Abstract Factory` when you want centralized, controlled object creation rather than cloning.
216+
- Serialization / deserialization can sometimes be used to create independent copies.
217+
218+
## Impact Assessment (required)
219+
220+
Before adopting Prototype, confirm:
221+
222+
- How copies handle state that crosses API boundaries—ensure compatibility for serialization and external contracts.
223+
- Whether cloned objects need new identity semantics (IDs, handles).
224+
- Thread-safety of prototype registration and clone operations.
225+
226+
Document these decisions in design docs, tests, and API contracts.
227+
228+
## Conclusion
229+
230+
- The Prototype pattern uses cloning to produce new objects and can reduce initialization cost and increase runtime flexibility.
231+
- Pay careful attention to copy semantics, external resources, identity, and concurrency.
232+
- In C++, returning `std::unique_ptr` from `Clone()` is a practical design; in dynamic languages like Python, the `copy` module or custom `clone()` implementations are convenient.
233+
234+
References (primary)
235+
236+
[!CARD](https://en.wikipedia.org/wiki/Prototype_pattern)
237+
"In software engineering, the prototype pattern is a creational design pattern in which new objects are created by copying a prototypical instance."
238+
(Source: https://en.wikipedia.org/wiki/Prototype_pattern)
239+
240+
(Secondary) For C++ copy/move semantics and smart pointers, consult the official reference:
241+
[!CARD](https://en.cppreference.com/)
242+
243+
That's it — I'll cover another GoF pattern next time. The sample code here is simplified for learning; evaluate and adapt thoroughly before using in production.
244+
245+
— Pan-kun!

0 commit comments

Comments
 (0)