Prototype Pattern: Clone Đối Tượng Hiệu Quả

Giới Thiệu
Một số đối tượng rất tốn kém để tạo ra — chúng yêu cầu truy vấn cơ sở dữ liệu, cấu hình phức tạp, hoặc một loạt các tính toán để đạt được trạng thái hợp lệ. Khi bạn đã có một instance tốt, tại sao lại bỏ đi và xây dựng lại từ đầu?
Prototype Pattern trả lời câu hỏi này: thay vì tạo đối tượng mới, hãy clone một đối tượng hiện có và chỉ sửa đổi những gì khác biệt. Pattern này đơn giản một cách đáng ngờ nhưng lại mạnh mẽ đến ngạc nhiên.
Bạn Sẽ Học Được Gì
✅ Hiểu vấn đề mà Prototype pattern giải quyết
✅ Triển khai shallow copy vs deep copy đúng cách
✅ Xây dựng Prototype Registry để quản lý việc clone
✅ Áp dụng pattern trong TypeScript, Python và Java
✅ Nhận biết Prototype trong thực tế (prototypal inheritance của JavaScript, game engine)
✅ Biết khi nào dùng Prototype so với các creational pattern khác
Điều Kiện Tiên Quyết
- Đã hoàn thành Builder Pattern Explained
- Quen với Factory và Abstract Factory Patterns
- Thành thạo classes và objects (Classes, Objects, and Abstraction)
Vấn Đề: Tạo Đối Tượng Tốn Kém
Hãy xét một game với các unit kẻ địch. Mỗi Enemy có stats, cây hành vi AI, textures, đồ thị tìm đường và profile âm thanh — tất cả được tải từ đĩa và tính toán khi khởi tạo:
class Enemy {
stats: Stats;
behaviorTree: BehaviorTree;
textures: TextureSet;
pathfindingGraph: Graph;
constructor(config: EnemyConfig) {
this.stats = loadStatsFromDisk(config.statFile); // chậm
this.behaviorTree = parseBehaviorXML(config.aiFile); // chậm
this.textures = loadTextures(config.textureDir); // rất chậm
this.pathfindingGraph = computeGraph(config.mapData); // tốn CPU
}
}
// Spawn 50 kẻ địch mỗi đợt = 50 × (tất cả khởi tạo tốn kém đó)
for (let i = 0; i < 50; i++) {
enemies.push(new Enemy(goblinConfig)); // ❌ cực kỳ chậm
}Giải pháp: tải một Enemy một lần, rồi clone thêm 49 cái:
const template = new Enemy(goblinConfig); // ✅ tải một lần
for (let i = 0; i < 50; i++) {
const enemy = template.clone(); // sao chép nhanh
enemy.position = randomSpawnPoint();
enemies.push(enemy);
}Đây là Prototype pattern trong một câu: tạo đối tượng mới bằng cách sao chép một prototype.
Cấu Trúc Pattern
Prototype pattern có cấu trúc tối giản:
Các thành phần chính:
- Prototype — interface khai báo phương thức
clone() - ConcretePrototype — triển khai
clone(), biết cách sao chép bản thân - Client — gọi
clone()thay vì gọinew
Pattern ủy thác trách nhiệm sao chép cho chính đối tượng — vì nó hiểu trạng thái nội tại của mình nhất.
Shallow Copy vs Deep Copy
Đây là khái niệm quan trọng nhất trong Prototype pattern. Nắm sai điều này sẽ tạo ra các bug tinh vi, khó debug.
Shallow Copy
Shallow copy nhân đôi các field cấp trên cùng của đối tượng. Các tham chiếu (con trỏ) đến đối tượng lồng nhau được sao chép — nhưng không phải bản thân đối tượng lồng nhau. Cả bản gốc và bản clone đều trỏ đến cùng một đối tượng lồng nhau.
class Address {
constructor(
public street: string,
public city: string
) {}
}
class User {
constructor(
public name: string,
public address: Address
) {}
shallowClone(): User {
return Object.assign(new User("", new Address("", "")), this);
}
}
const original = new User("Alice", new Address("123 Main St", "Springfield"));
const clone = original.shallowClone();
clone.name = "Bob"; // ✅ độc lập — giá trị primitive
clone.address.city = "Shelbyville"; // ❌ cũng thay đổi original.address.city!
console.log(original.address.city); // "Shelbyville" — oops!Minh họa shallow copy:
Deep Copy
Deep copy đệ quy nhân đôi tất cả đối tượng lồng nhau. Bản clone hoàn toàn độc lập.
class Address {
constructor(
public street: string,
public city: string
) {}
clone(): Address {
return new Address(this.street, this.city);
}
}
class User {
constructor(
public name: string,
public address: Address
) {}
deepClone(): User {
return new User(this.name, this.address.clone()); // clone cả đối tượng lồng nhau
}
}
const original = new User("Alice", new Address("123 Main St", "Springfield"));
const clone = original.deepClone();
clone.name = "Bob";
clone.address.city = "Shelbyville";
console.log(original.address.city); // "Springfield" ✅ bản gốc không bị thay đổiKhi Nào Dùng Loại Nào
| Trường hợp | Dùng |
|---|---|
| Tất cả field là primitive (string, number, boolean) | Shallow copy an toàn |
| Field là tham chiếu đến đối tượng immutable | Shallow copy an toàn |
| Field là tham chiếu đến đối tượng mutable | Cần deep copy |
| Đối tượng lồng nhau chứa đối tượng lồng nhau | Cần deep copy |
| Hiệu năng quan trọng và không có mutation | Shallow copy để tăng tốc |
Triển Khai TypeScript
Prototype Cơ Bản với Interface
interface Cloneable<T> {
clone(): T;
}
class GameCharacter implements Cloneable<GameCharacter> {
constructor(
public name: string,
public health: number,
public attack: number,
public skills: string[], // mảng mutable — cần deep copy
public position: { x: number; y: number } // object mutable — cần deep copy
) {}
clone(): GameCharacter {
return new GameCharacter(
this.name,
this.health,
this.attack,
[...this.skills], // deep copy mảng
{ ...this.position } // deep copy object
);
}
}
// Sử dụng
const goblinTemplate = new GameCharacter(
"Goblin",
100,
15,
["slash", "dodge"],
{ x: 0, y: 0 }
);
const goblin1 = goblinTemplate.clone();
goblin1.position = { x: 10, y: 20 };
goblin1.health = 80; // bị thương
const goblin2 = goblinTemplate.clone();
goblin2.position = { x: 50, y: 30 };
// Template không bị thay đổi
console.log(goblinTemplate.health); // 100
console.log(goblinTemplate.position); // { x: 0, y: 0 }Prototype Registry
Prototype Registry (còn gọi là Prototype Store hay Cache) duy trì một tập hợp các prototype đã được xây dựng sẵn, được đánh key theo tên. Client yêu cầu bản clone theo key:
class CharacterRegistry {
private prototypes = new Map<string, GameCharacter>();
register(key: string, prototype: GameCharacter): void {
this.prototypes.set(key, prototype);
}
create(key: string): GameCharacter {
const prototype = this.prototypes.get(key);
if (!prototype) {
throw new Error(`Prototype '${key}' không tìm thấy trong registry`);
}
return prototype.clone();
}
}
// Thiết lập registry một lần (ví dụ: khi tải game)
const registry = new CharacterRegistry();
registry.register("goblin", new GameCharacter("Goblin", 100, 15, ["slash"], { x: 0, y: 0 }));
registry.register("troll", new GameCharacter("Troll", 500, 40, ["smash", "roar"], { x: 0, y: 0 }));
registry.register("archer", new GameCharacter("Archer", 80, 25, ["shoot", "dodge"], { x: 0, y: 0 }));
// Spawn kẻ địch hiệu quả
function spawnWave(type: string, count: number, spawnPoints: { x: number; y: number }[]) {
return spawnPoints.slice(0, count).map((pos, i) => {
const enemy = registry.create(type);
enemy.position = pos;
enemy.name = `${enemy.name} #${i + 1}`;
return enemy;
});
}
const wave = spawnWave("goblin", 10, generateSpawnPoints());Ví Dụ Template Tài Liệu
Một trường hợp thực tế phổ biến: template email/tài liệu.
interface Section {
title: string;
content: string;
clone(): Section;
}
class TextSection implements Section {
constructor(
public title: string,
public content: string
) {}
clone(): TextSection {
return new TextSection(this.title, this.content);
}
}
class Document {
constructor(
public title: string,
public author: string,
public sections: Section[],
public metadata: Record<string, string>
) {}
clone(): Document {
return new Document(
this.title,
this.author,
this.sections.map(s => s.clone()), // deep clone từng section
{ ...this.metadata } // shallow clone (giá trị là string)
);
}
}
// Tạo document template
const reportTemplate = new Document(
"Báo Cáo Tháng",
"Template",
[
new TextSection("Tóm Tắt Điều Hành", "Nhập tóm tắt tại đây..."),
new TextSection("Các Chỉ Số Chính", "Nhập chỉ số tại đây..."),
new TextSection("Kết Luận", "Nhập kết luận tại đây..."),
],
{ company: "Acme Corp", confidential: "true" }
);
// Tạo báo cáo tháng này từ template
const janReport = reportTemplate.clone();
janReport.title = "Báo Cáo Tháng - Tháng 1 2026";
janReport.author = "Nguyễn Văn A";
janReport.sections[0].content = "Tháng 1 là tháng phá kỷ lục...";
janReport.metadata.period = "Tháng 1 2026";
// Template vẫn nguyên vẹn cho tháng sau
console.log(reportTemplate.title); // "Báo Cáo Tháng" — không thay đổiTriển Khai Python
Python cung cấp hỗ trợ tích hợp cho shallow và deep copy qua module copy.
import copy
from dataclasses import dataclass, field
from typing import Protocol
class Cloneable(Protocol):
def clone(self) -> "Cloneable":
...
@dataclass
class Skill:
name: str
damage: int
cooldown: float
def clone(self) -> "Skill":
return Skill(self.name, self.damage, self.cooldown)
@dataclass
class GameCharacter:
name: str
health: int
attack: int
skills: list[Skill] = field(default_factory=list)
position: dict = field(default_factory=lambda: {"x": 0, "y": 0})
def shallow_clone(self) -> "GameCharacter":
"""Shallow copy — danh sách skills được chia sẻ!"""
return copy.copy(self)
def clone(self) -> "GameCharacter":
"""Deep copy — hoàn toàn độc lập"""
return copy.deepcopy(self)
def manual_clone(self) -> "GameCharacter":
"""Deep clone thủ công — kiểm soát nhiều hơn"""
return GameCharacter(
name=self.name,
health=self.health,
attack=self.attack,
skills=[skill.clone() for skill in self.skills],
position=dict(self.position)
)
# Bug với shallow copy
goblin_template = GameCharacter(
name="Goblin",
health=100,
attack=15,
skills=[Skill("slash", 20, 1.5), Skill("dodge", 0, 3.0)]
)
shallow = goblin_template.shallow_clone()
shallow.skills.append(Skill("backstab", 35, 5.0))
print(len(goblin_template.skills)) # 3 — template bị sửa đổi! Bug!
# Deep copy — an toàn
goblin_template2 = GameCharacter(
name="Goblin",
health=100,
attack=15,
skills=[Skill("slash", 20, 1.5), Skill("dodge", 0, 3.0)]
)
deep = goblin_template2.clone()
deep.skills.append(Skill("backstab", 35, 5.0))
print(len(goblin_template2.skills)) # 2 — template không bị thay đổi ✅Python Prototype Registry
from typing import Dict, TypeVar
T = TypeVar("T", bound=GameCharacter)
class CharacterRegistry:
def __init__(self):
self._prototypes: Dict[str, GameCharacter] = {}
def register(self, key: str, prototype: GameCharacter) -> None:
self._prototypes[key] = prototype
def create(self, key: str, **overrides) -> GameCharacter:
if key not in self._prototypes:
raise KeyError(f"Prototype '{key}' chưa được đăng ký")
char = self._prototypes[key].clone()
for attr, value in overrides.items():
setattr(char, attr, value)
return char
# Sử dụng
registry = CharacterRegistry()
registry.register("goblin", GameCharacter("Goblin", 100, 15, [Skill("slash", 20, 1.5)]))
registry.register("elite_goblin", GameCharacter("Elite Goblin", 200, 30, [Skill("slash", 30, 1.0), Skill("shield", 0, 4.0)]))
# Spawn với tùy chỉnh
g1 = registry.create("goblin", name="Goblin Scout", position={"x": 10, "y": 5})
g2 = registry.create("goblin", health=50) # biến thể yếu hơn
print(g1.name) # Goblin Scout
print(g2.health) # 50Dùng Hook __copy__ và __deepcopy__
Python cho phép bạn tùy chỉnh hành vi copy với các phương thức đặc biệt:
import copy
class Configuration:
def __init__(self, settings: dict, cache: dict = None):
self.settings = settings
self.cache = cache or {} # cache KHÔNG nên được sao chép
def __copy__(self):
"""Shallow copy: chia sẻ cache"""
new_obj = Configuration.__new__(Configuration)
new_obj.__dict__.update(self.__dict__)
return new_obj
def __deepcopy__(self, memo):
"""Deep copy: cache mới, deep copy settings"""
new_obj = Configuration.__new__(Configuration)
memo[id(self)] = new_obj
new_obj.settings = copy.deepcopy(self.settings, memo)
new_obj.cache = {} # không sao chép cache — có chủ đích
return new_obj
config = Configuration({"theme": "dark", "language": "en"})
config.cache["user_prefs"] = {"font_size": 14}
cloned = copy.deepcopy(config)
cloned.settings["theme"] = "light"
print(config.settings["theme"]) # "dark" — bản gốc không thay đổi ✅
print(len(cloned.cache)) # 0 — cache không được sao chép như ý ✅Triển Khai Java
Java có interface Cloneable và phương thức Object.clone() tích hợp sẵn, dù chúng đi kèm với một số cạm bẫy.
Dùng Cloneable (Cách Cổ Điển)
import java.util.ArrayList;
import java.util.List;
public class GameCharacter implements Cloneable {
private String name;
private int health;
private int attack;
private List<String> skills; // mutable — cần deep copy
public GameCharacter(String name, int health, int attack, List<String> skills) {
this.name = name;
this.health = health;
this.attack = attack;
this.skills = skills;
}
@Override
public GameCharacter clone() {
try {
GameCharacter clone = (GameCharacter) super.clone(); // shallow copy primitives
clone.skills = new ArrayList<>(this.skills); // deep copy danh sách
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError("Không bao giờ xảy ra", e);
}
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getHealth() { return health; }
public void setHealth(int health) { this.health = health; }
public List<String> getSkills() { return skills; }
}
// Sử dụng
GameCharacter goblinTemplate = new GameCharacter(
"Goblin", 100, 15,
new ArrayList<>(List.of("slash", "dodge"))
);
GameCharacter goblin1 = goblinTemplate.clone();
goblin1.setName("Goblin Scout");
goblin1.getSkills().add("backstab");
// Template không bị ảnh hưởng
System.out.println(goblinTemplate.getName()); // "Goblin"
System.out.println(goblinTemplate.getSkills().size()); // 2Lưu ý:
Cloneablecủa Java được coi là interface thiết kế kém. Bản thân nó không có phương thứcclone()— nó chỉ báo hiệu choObject.clone()rằng việc clone được cho phép. Pattern copy constructor bên dưới thường được ưa chuộng hơn trong Java hiện đại.
Pattern Copy Constructor (Ưa Thích Trong Java Hiện Đại)
public class GameCharacter {
private final String name;
private final int health;
private final int attack;
private final List<String> skills;
// Constructor thông thường
public GameCharacter(String name, int health, int attack, List<String> skills) {
this.name = name;
this.health = health;
this.attack = attack;
this.skills = List.copyOf(skills); // bản sao immutable
}
// Copy constructor — rõ ràng và tường minh
public GameCharacter(GameCharacter other) {
this(other.name, other.health, other.attack, other.skills);
}
// Sửa đổi theo kiểu builder — trả về instance mới
public GameCharacter withName(String name) {
return new GameCharacter(name, this.health, this.attack, this.skills);
}
public GameCharacter withHealth(int health) {
return new GameCharacter(this.name, health, this.attack, this.skills);
}
public GameCharacter addSkill(String skill) {
List<String> newSkills = new ArrayList<>(this.skills);
newSkills.add(skill);
return new GameCharacter(this.name, this.health, this.attack, newSkills);
}
public String getName() { return name; }
public int getHealth() { return health; }
public List<String> getSkills() { return skills; }
}
// Sử dụng — immutable + copy constructor = clone sạch sẽ
GameCharacter goblinTemplate = new GameCharacter(
"Goblin", 100, 15, List.of("slash", "dodge")
);
GameCharacter scout = new GameCharacter(goblinTemplate)
.withName("Goblin Scout")
.addSkill("backstab");
GameCharacter weakened = new GameCharacter(goblinTemplate)
.withHealth(50);
System.out.println(goblinTemplate.getHealth()); // 100 ✅
System.out.println(scout.getSkills()); // [slash, dodge, backstab]
System.out.println(weakened.getHealth()); // 50Java Prototype Registry
import java.util.HashMap;
import java.util.Map;
public class CharacterRegistry {
private final Map<String, GameCharacter> prototypes = new HashMap<>();
public void register(String key, GameCharacter prototype) {
prototypes.put(key, prototype);
}
public GameCharacter create(String key) {
GameCharacter prototype = prototypes.get(key);
if (prototype == null) {
throw new IllegalArgumentException("Prototype không tìm thấy: " + key);
}
return new GameCharacter(prototype); // copy constructor
}
}
public class Game {
public static void main(String[] args) {
CharacterRegistry registry = new CharacterRegistry();
registry.register("goblin",
new GameCharacter("Goblin", 100, 15, List.of("slash")));
registry.register("troll",
new GameCharacter("Troll", 500, 40, List.of("smash", "roar")));
// Spawn đợt tấn công
List<GameCharacter> wave = new ArrayList<>();
for (int i = 0; i < 20; i++) {
wave.add(registry.create("goblin"));
}
for (int i = 0; i < 5; i++) {
wave.add(registry.create("troll"));
}
System.out.println("Đã spawn: " + wave.size() + " kẻ địch");
}
}Prototypal Inheritance của JavaScript
JavaScript độc đáo: nó sử dụng Prototype ở cấp độ ngôn ngữ. Mỗi object đều có liên kết [[Prototype]] đến một object khác. Đây không phải là GoF Prototype pattern — đây là một khái niệm khác có cùng tên.
// Prototypal inheritance tích hợp sẵn của JavaScript
const animalProto = {
speak() {
return `${this.name} nói ${this.sound}`;
},
eat() {
return `${this.name} đang ăn`;
}
};
// Object.create() triển khai prototypal inheritance
const dog = Object.create(animalProto);
dog.name = "Rex";
dog.sound = "Gâu";
console.log(dog.speak()); // "Rex nói Gâu"
console.log(Object.getPrototypeOf(dog) === animalProto); // true
// Class hiện đại là syntactic sugar trên prototype chain này
class Animal {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
speak() {
return `${this.name} nói ${this.sound}`;
}
}
class Dog extends Animal {
constructor(name) {
super(name, "Gâu");
}
fetch() { return `${this.name} lấy bóng!`; }
}
// Phía sau, Dog.prototype được liên kết với Animal.prototype
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // trueGoF Prototype Pattern trong JavaScript
Để triển khai GoF pattern (cloning) trong JavaScript:
// Dùng structuredClone (Node 17+, browser hiện đại) — deep clone
const template = {
type: "enemy",
stats: { health: 100, attack: 15 },
skills: ["slash", "dodge"],
clone() {
return structuredClone(this);
}
};
const enemy1 = template.clone();
enemy1.stats.health = 80; // bị thương
console.log(template.stats.health); // 100 ✅ — structuredClone là deep copy thực sự
// Cách cũ: JSON round-trip (dùng được cho plain data, mất methods)
const deepCopy = JSON.parse(JSON.stringify(template));Các Trường Hợp Thực Tế
1. Configuration Preset
class ServerConfig {
constructor(
public host: string,
public port: number,
public timeout: number,
public retries: number,
public headers: Record<string, string>,
public features: string[]
) {}
clone(): ServerConfig {
return new ServerConfig(
this.host,
this.port,
this.timeout,
this.retries,
{ ...this.headers },
[...this.features]
);
}
withTimeout(timeout: number): ServerConfig {
const clone = this.clone();
clone.timeout = timeout;
return clone;
}
withFeature(feature: string): ServerConfig {
const clone = this.clone();
clone.features.push(feature);
return clone;
}
}
// Config cơ sở
const baseConfig = new ServerConfig(
"api.example.com", 443, 5000, 3,
{ "Content-Type": "application/json" },
["auth", "logging"]
);
// Các config dẫn xuất
const fastConfig = baseConfig.withTimeout(1000);
const debugConfig = baseConfig.withFeature("verbose-logging").withFeature("request-tracing");
const productionConfig = baseConfig.withTimeout(3000).withFeature("rate-limiting");2. Template Component UI
interface ComponentConfig {
styles: Record<string, string>;
classes: string[];
attributes: Record<string, string>;
clone(): ComponentConfig;
}
class ButtonConfig implements ComponentConfig {
constructor(
public styles: Record<string, string>,
public classes: string[],
public attributes: Record<string, string>
) {}
clone(): ButtonConfig {
return new ButtonConfig(
{ ...this.styles },
[...this.classes],
{ ...this.attributes }
);
}
}
// Component cơ sở trong design system
const primaryButton = new ButtonConfig(
{ background: "#3b82f6", color: "white", padding: "8px 16px" },
["btn", "btn-primary"],
{ type: "button", role: "button" }
);
// Các biến thể từ prototype
const dangerButton = primaryButton.clone();
dangerButton.styles.background = "#ef4444";
dangerButton.classes.push("btn-danger");
const disabledButton = primaryButton.clone();
disabledButton.styles.opacity = "0.5";
disabledButton.attributes.disabled = "true";
disabledButton.attributes["aria-disabled"] = "true";Khi Nào Dùng Prototype Pattern
Dùng Prototype khi:
- Tạo đối tượng tốn kém (truy vấn DB, network call, file I/O, tính toán nặng)
- Cần nhiều đối tượng tương tự chỉ khác nhau ở một vài field
- Class cần khởi tạo được xác định lúc runtime (qua registry)
- Muốn tránh xây dựng phân cấp class phức tạp của factory
- Đối tượng có nhiều field tùy chọn và bạn muốn các preset cấu hình
Tránh Prototype khi:
- Tạo đối tượng rẻ — chỉ cần dùng
new - Tất cả field phải được đặt mới — cloning không mang lại lợi ích
- Deep copy phức tạp hoặc có tác dụng phụ (kết nối DB, file handle, thread)
- Bạn cần một kiểu hoàn toàn khác, không phải bản sao của một instance hiện có
Prototype vs Các Creational Pattern Khác
| Pattern | Tạo Object Qua | Phù Hợp Nhất Cho |
|---|---|---|
| Factory Method | Subclass quyết định | Các loại sản phẩm khác nhau |
| Abstract Factory | Gia đình factory | Các gia đình sản phẩm liên quan |
| Builder | Xây dựng từng bước | Object phức tạp với nhiều phần tùy chọn |
| Prototype | Clone instance hiện có | Bản sao của object tốn kém để tạo |
| Singleton | Instance được chia sẻ duy nhất | Cần chính xác một instance |
Prototype và Builder đôi khi được kết hợp: xây dựng một prototype với Builder, rồi clone nhiều lần.
Những Cạm Bẫy Thường Gặp
1. Vô Tình Shallow Copy
// ❌ Bug: mảng và object được chia sẻ
class Config {
tags: string[] = [];
clone(): Config {
return Object.assign(new Config(), this); // shallow!
}
}
const base = new Config();
base.tags.push("prod");
const clone = base.clone();
clone.tags.push("debug"); // cũng sửa đổi base.tags!
console.log(base.tags); // ["prod", "debug"] — oops// ✅ Sửa: deep copy tất cả tham chiếu mutable
class Config {
tags: string[] = [];
clone(): Config {
const c = new Config();
c.tags = [...this.tags]; // sao chép mảng
return c;
}
}2. Tham Chiếu Vòng Tròn Trong Deep Copy
class Node {
next: Node | null = null;
prev: Node | null = null; // vòng tròn!
deepClone(): Node {
// deepClone ngây thơ sẽ gây đệ quy vô hạn
// Dùng Map để theo dõi các node đã thăm
const visited = new Map<Node, Node>();
function cloneNode(node: Node | null): Node | null {
if (!node) return null;
if (visited.has(node)) return visited.get(node)!;
const cloned = new Node();
visited.set(node, cloned);
cloned.next = cloneNode(node.next);
cloned.prev = cloneNode(node.prev);
return cloned;
}
return cloneNode(this)!;
}
}3. Clone Object Có Resource Không Thể Clone
class DatabaseConnection {
private connection: pg.Client; // không thể clone!
constructor(config: DBConfig) {
this.connection = new pg.Client(config);
}
// ❌ Đừng clone cái này — clone config, tạo connection mới
clone(): DatabaseConnection {
// Điều này sẽ chia sẻ cùng một socket — nguy hiểm
return Object.assign(new DatabaseConnection({} as DBConfig), this);
}
}
// ✅ Tốt hơn: prototype lưu config, không phải live connection
class DBConnectionFactory {
constructor(private config: DBConfig) {}
create(): pg.Client {
return new pg.Client({ ...this.config }); // connection mới mỗi lần
}
}Tóm Tắt
Prototype pattern là creational pattern đơn giản nhất — clone thay vì construct. Sức mạnh của nó nằm ở:
- Hiệu năng: Tránh khởi tạo tốn kém bằng cách sao chép prototype đã được xây dựng sẵn
- Linh hoạt: Sửa đổi bản clone tự do mà không ảnh hưởng đến bản gốc
- Đơn giản: Registry + clone thường đơn giản hơn factory với phân cấp phức tạp
Những điểm chính:
✅ Luôn phân biệt shallow copy và deep copy — hầu hết bug đến từ việc vô tình chia sẻ tham chiếu mutable
✅ Dùng Prototype Registry để quản lý và truy xuất các prototype theo tên
✅ Trong Python, ưu tiên copy.deepcopy() hoặc triển khai __deepcopy__ để kiểm soát đầy đủ
✅ Trong Java, ưu tiên copy constructor thay vì Cloneable (được coi là thiết kế kém)
✅ Trong JavaScript, structuredClone() là cách hiện đại, đúng đắn để deep clone
✅ Chú ý tham chiếu vòng tròn và resource không thể clone (file handle, kết nối DB)
Tiếp Theo Là Gì
Tiếp theo trong series OOP & Design Patterns:
- OOP-12: Adapter và Facade Patterns — structural pattern cho interface không tương thích
- OOP-13: Decorator và Proxy Patterns — bọc đối tượng để mở rộng hoặc kiểm soát hành vi
- OOP-14: Strategy và Template Method — behavioral pattern cho thuật toán
Bài Tập Thực Hành
-
Game Spawner: Xây dựng
MonsterRegistrygiữ 5 prototype monster khác nhau (mỗi cái có health, attack, skills và loot table). Triển khai hàmspawnWave(type, count)clone và đặt vị trí chúng trên grid 100×100. -
Email Template Engine: Tạo class
EmailTemplatevới subject, body (có biến{{placeholder}}), và danh sách người nhận. Triển khaiclone()và phương thứcfill(data: Record<string, string>)trả về email mới với placeholder đã được thay thế. -
Config Builder: Triển khai
HttpClientConfigvới base URL, timeout, retry count và default headers. Xây dựng ba preset config (fast, standard, resilient) được dẫn xuất từ prototype cơ sở, mỗi cái có giá trị timeout/retry khác nhau. -
Circular List Clone: Triển khai doubly linked circular list (
head.prev = tail,tail.next = head) và viếtdeepClone()xử lý đúng tham chiếu vòng tròn mà không gây đệ quy vô hạn.
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.