Back to blog

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

oopcreational-patternsdesign-patternstypescriptpythonjava
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


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ọi new

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 đổi

Khi Nào Dùng Loại Nào

Trường hợpDùng
Tất cả field là primitive (string, number, boolean)Shallow copy an toàn
Field là tham chiếu đến đối tượng immutableShallow copy an toàn
Field là tham chiếu đến đối tượng mutableCần deep copy
Đối tượng lồng nhau chứa đối tượng lồng nhauCần deep copy
Hiệu năng quan trọng và không có mutationShallow 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 đổi

Triể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)  # 50

Dù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()); // 2

Lưu ý: Cloneable của Java được coi là interface thiết kế kém. Bản thân nó không có phương thức clone() — nó chỉ báo hiệu cho Object.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());         // 50

Java 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); // true

GoF 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

PatternTạo Object QuaPhù Hợp Nhất Cho
Factory MethodSubclass quyết địnhCác loại sản phẩm khác nhau
Abstract FactoryGia đình factoryCác gia đình sản phẩm liên quan
BuilderXây dựng từng bướcObject phức tạp với nhiều phần tùy chọn
PrototypeClone instance hiện cóBản sao của object tốn kém để tạo
SingletonInstance được chia sẻ duy nhấtCầ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 ở:

  1. 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
  2. Linh hoạt: Sửa đổi bản clone tự do mà không ảnh hưởng đến bản gốc
  3. Đơ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

  1. Game Spawner: Xây dựng MonsterRegistry giữ 5 prototype monster khác nhau (mỗi cái có health, attack, skills và loot table). Triển khai hàm spawnWave(type, count) clone và đặt vị trí chúng trên grid 100×100.

  2. Email Template Engine: Tạo class EmailTemplate với subject, body (có biến {{placeholder}}), và danh sách người nhận. Triển khai clone() và phương thức fill(data: Record<string, string>) trả về email mới với placeholder đã được thay thế.

  3. Config Builder: Triển khai HttpClientConfig vớ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.

  4. Circular List Clone: Triển khai doubly linked circular list (head.prev = tail, tail.next = head) và viết deepClone() 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.