Complex Object Duplication
Clone objects with complex initialization or configuration.
Ce contenu n’est pas encore disponible dans votre langue.
The Prototype is a creational design pattern that lets you copy existing objects without making your code dependent on their classes. It creates new objects by cloning existing prototypes rather than instantiating new ones.
The Prototype pattern specifies the kinds of objects to create using a prototypical instance, and creates new objects by copying this prototype. This is particularly useful when object creation is costly or complex.
Complex Object Duplication
Clone objects with complex initialization or configuration.
Performance Optimization
Avoid expensive initialization by cloning pre-configured objects.
Dynamic Object Creation
Create objects at runtime without knowing their concrete classes.
Snapshot/Memento
Save object states for undo/redo functionality.
| Advantages ✅ | Disadvantages ❌ |
|---|---|
| Reduces object creation cost. | Cloning complex objects with circular references is tricky. |
| Avoids subclass proliferation. | Deep copying can be complex. |
| Adds/removes objects at runtime. | Requires implementing clone method for each class. |
| Specifies new objects by varying values. | Can be difficult with objects having private fields. |
Understanding the difference between shallow and deep copying is crucial:
// Prototype Interfaceinterface Prototype {clone(): Prototype;}
// Concrete Prototypeclass Shape implements Prototype {x: number;y: number;color: string;
constructor(x: number, y: number, color: string) {this.x = x;this.y = y;this.color = color;}
clone(): Shape {return new Shape(this.x, this.y, this.color);}
describe(): string {return `Shape at (${this.x}, ${this.y}) with color ${this.color}`;}}
class Circle extends Shape {radius: number;
constructor(x: number, y: number, color: string, radius: number) {super(x, y, color);this.radius = radius;}
clone(): Circle {return new Circle(this.x, this.y, this.color, this.radius);}
describe(): string {return `Circle at (${this.x}, ${this.y}) with radius ${this.radius} and color ${this.color}`;}}
// Usageconst originalCircle = new Circle(10, 20, "red", 5);const clonedCircle = originalCircle.clone();
clonedCircle.x = 30;clonedCircle.color = "blue";
console.log(originalCircle.describe());// Circle at (10, 20) with radius 5 and color red
console.log(clonedCircle.describe());// Circle at (30, 20) with radius 5 and color blue// Prototype Interfaceinterface Prototype extends Cloneable {Prototype clone();}
// Concrete Prototypeclass Shape implements Prototype {protected int x;protected int y;protected String color;
public Shape(int x, int y, String color) {this.x = x;this.y = y;this.color = color;}
@Overridepublic Shape clone() {return new Shape(this.x, this.y, this.color);}
public String describe() {return String.format("Shape at (%d, %d) with color %s", x, y, color);}}
class Circle extends Shape {private int radius;
public Circle(int x, int y, String color, int radius) {super(x, y, color);this.radius = radius;}
@Overridepublic Circle clone() {return new Circle(this.x, this.y, this.color, this.radius);}
@Overridepublic String describe() {return String.format("Circle at (%d, %d) with radius %d and color %s",x, y, radius, color);}}
// UsageCircle originalCircle = new Circle(10, 20, "red", 5);Circle clonedCircle = originalCircle.clone();
clonedCircle.x = 30;clonedCircle.color = "blue";
System.out.println(originalCircle.describe());System.out.println(clonedCircle.describe());// Prototype Interfaceinterface PrototypeInterface {public function clone(): self;}
// Concrete Prototypeclass Shape implements PrototypeInterface {protected $x;protected $y;protected $color;
public function __construct(int $x, int $y, string $color) {$this->x = $x;$this->y = $y;$this->color = $color;}
public function clone(): self {return new self($this->x, $this->y, $this->color);}
public function describe(): string {return sprintf("Shape at (%d, %d) with color %s",$this->x, $this->y, $this->color);}}
class Circle extends Shape {private $radius;
public function __construct(int $x, int $y, string $color, int $radius) {parent::__construct($x, $y, $color);$this->radius = $radius;}
public function clone(): self {return new self($this->x, $this->y, $this->color, $this->radius);}
public function describe(): string {return sprintf("Circle at (%d, %d) with radius %d and color %s",$this->x, $this->y, $this->radius, $this->color);}}
// Usage$originalCircle = new Circle(10, 20, "red", 5);$clonedCircle = $originalCircle->clone();
echo $originalCircle->describe() . "\n";echo $clonedCircle->describe() . "\n";// Prototype Traittrait Prototype: Clone {fn clone_prototype(&self) -> Self;}
// Concrete Prototype#[derive(Clone)]struct Shape {x: i32,y: i32,color: String,}
impl Shape {fn new(x: i32, y: i32, color: String) -> Self {Shape { x, y, color }}
fn describe(&self) -> String {format!("Shape at ({}, {}) with color {}", self.x, self.y, self.color)}}
impl Prototype for Shape {fn clone_prototype(&self) -> Self {self.clone()}}
#[derive(Clone)]struct Circle {x: i32,y: i32,color: String,radius: i32,}
impl Circle {fn new(x: i32, y: i32, color: String, radius: i32) -> Self {Circle { x, y, color, radius }}
fn describe(&self) -> String {format!("Circle at ({}, {}) with radius {} and color {}",self.x, self.y, self.radius, self.color)}}
impl Prototype for Circle {fn clone_prototype(&self) -> Self {self.clone()}}
// Usagefn main() {let original_circle = Circle::new(10, 20, "red".to_string(), 5);let mut cloned_circle = original_circle.clone_prototype();
cloned_circle.x = 30;cloned_circle.color = "blue".to_string();
println!("{}", original_circle.describe());println!("{}", cloned_circle.describe());}interface ComponentState { clone(): ComponentState;}
class FormState implements ComponentState { fields: Map<string, any>; isDirty: boolean; errors: Map<string, string>;
constructor() { this.fields = new Map(); this.isDirty = false; this.errors = new Map(); }
clone(): FormState { const cloned = new FormState(); cloned.fields = new Map(this.fields); cloned.isDirty = this.isDirty; cloned.errors = new Map(this.errors); return cloned; }
setField(name: string, value: any): void { this.fields.set(name, value); this.isDirty = true; }
getField(name: string): any { return this.fields.get(name); }
setError(field: string, message: string): void { this.errors.set(field, message); }}
export { FormState, ComponentState };<script setup lang="ts">import { ref } from 'vue';import { FormState } from './ComponentStatePrototype';
const currentState = ref(new FormState());const history = ref<FormState[]>([]);const historyIndex = ref(-1);
const saveState = () => { // Remove any forward history history.value = history.value.slice(0, historyIndex.value + 1);
// Add current state to history history.value.push(currentState.value.clone()); historyIndex.value++;};
const updateField = (name: string, value: any) => { saveState(); currentState.value.setField(name, value);};
const undo = () => { if (historyIndex.value > 0) { historyIndex.value--; currentState.value = history.value[historyIndex.value].clone(); }};
const redo = () => { if (historyIndex.value < history.value.length - 1) { historyIndex.value++; currentState.value = history.value[historyIndex.value].clone(); }};
const canUndo = computed(() => historyIndex.value > 0);const canRedo = computed(() => historyIndex.value < history.value.length - 1);</script>
<template> <div> <button @click="undo" :disabled="!canUndo">Undo</button> <button @click="redo" :disabled="!canRedo">Redo</button>
<input :value="currentState.getField('name')" @input="updateField('name', ($event.target as HTMLInputElement).value)" placeholder="Name" /> </div></template>class Address {street: string;city: string;country: string;
constructor(street: string, city: string, country: string) {this.street = street;this.city = city;this.country = country;}
clone(): Address {return new Address(this.street, this.city, this.country);}}
class Person {name: string;age: number;address: Address;hobbies: string[];
constructor(name: string, age: number, address: Address, hobbies: string[]) {this.name = name;this.age = age;this.address = address;this.hobbies = hobbies;}
// Shallow clone - shares referencesshallowClone(): Person {return new Person(this.name, this.age, this.address, this.hobbies);}
// Deep clone - completely independentdeepClone(): Person {return new Person(this.name,this.age,this.address.clone(),[...this.hobbies]);}
describe(): string {return `${this.name}, ${this.age} years old, lives in ${this.address.city}`;}}
// Usage - Demonstrating the differenceconst originalAddress = new Address("123 Main St", "New York", "USA");const originalPerson = new Person("John Doe",30,originalAddress,["reading", "gaming"]);
// Shallow clone - address is sharedconst shallowClone = originalPerson.shallowClone();shallowClone.address.city = "Los Angeles";console.log(originalPerson.describe());// John Doe, 30 years old, lives in Los Angeles (MODIFIED!)
// Deep clone - address is independentconst deepClone = originalPerson.deepClone();deepClone.address.city = "Chicago";console.log(originalPerson.describe());// John Doe, 30 years old, lives in Los Angeles (NOT MODIFIED)console.log(deepClone.describe());// John Doe, 30 years old, lives in Chicago// Abstract Prototypeabstract class GameCharacter {name: string;health: number;attack: number;defense: number;
constructor(name: string, health: number, attack: number, defense: number) {this.name = name;this.health = health;this.attack = attack;this.defense = defense;}
abstract clone(): GameCharacter;
describe(): string {return `${this.name}: HP=${this.health}, ATK=${this.attack}, DEF=${this.defense}`;}}
// Concrete Prototypesclass Warrior extends GameCharacter {constructor() {super("Warrior", 150, 30, 25);}
clone(): Warrior {const cloned = new Warrior();cloned.name = this.name;cloned.health = this.health;cloned.attack = this.attack;cloned.defense = this.defense;return cloned;}}
class Mage extends GameCharacter {mana: number;
constructor() {super("Mage", 80, 50, 10);this.mana = 100;}
clone(): Mage {const cloned = new Mage();cloned.name = this.name;cloned.health = this.health;cloned.attack = this.attack;cloned.defense = this.defense;cloned.mana = this.mana;return cloned;}
describe(): string {return `${super.describe()}, MP=${this.mana}`;}}
class Archer extends GameCharacter {constructor() {super("Archer", 100, 40, 15);}
clone(): Archer {const cloned = new Archer();cloned.name = this.name;cloned.health = this.health;cloned.attack = this.attack;cloned.defense = this.defense;return cloned;}}
// Prototype Registryclass CharacterRegistry {private prototypes: Map<string, GameCharacter> = new Map();
registerPrototype(key: string, prototype: GameCharacter): void {this.prototypes.set(key, prototype);}
createCharacter(key: string): GameCharacter | undefined {const prototype = this.prototypes.get(key);return prototype ? prototype.clone() : undefined;}
listAvailable(): string[] {return Array.from(this.prototypes.keys());}}
// Usageconst registry = new CharacterRegistry();
// Register prototypesregistry.registerPrototype("warrior", new Warrior());registry.registerPrototype("mage", new Mage());registry.registerPrototype("archer", new Archer());
// Create characters from prototypesconst player1 = registry.createCharacter("warrior");const player2 = registry.createCharacter("mage");const player3 = registry.createCharacter("archer");
if (player1) console.log(player1.describe());if (player2) console.log(player2.describe());if (player3) console.log(player3.describe());
// Modify clones without affecting prototypesif (player1) {player1.name = "Player 1 Warrior";player1.health = 200;}
// Create another warrior - still has original statsconst player4 = registry.createCharacter("warrior");if (player4) console.log(player4.describe());// Warrior: HP=150, ATK=30, DEF=25Use Deep Copy Carefully
Deep copying can be expensive - use it only when necessary.
Handle Circular References
Implement safeguards against circular reference issues.
Consider Built-in Cloning
Use language built-in cloning mechanisms when available.
Document Clone Behavior
Clearly document whether cloning is shallow or deep.
const original = { name: "John", age: 30 };const clone = { ...original };const clone = Object.assign({}, original);// Limited: doesn't handle functions, dates, undefined, etc.const clone = JSON.parse(JSON.stringify(original));// Modern approach - handles most typesconst clone = structuredClone(original);| Prototype | Factory Method |
|---|---|
| Clones existing objects | Creates new objects from scratch |
| Runtime object creation | Compile-time class selection |
| Best for complex initialization | Best for simple instantiation |
| Requires existing instance | Doesn’t need existing instance |
class Document { title: string; content: string; metadata: Map<string, any>; createdAt: Date;
constructor(title: string, content: string) { this.title = title; this.content = content; this.metadata = new Map(); this.createdAt = new Date(); }
// Copy constructor static fromPrototype(prototype: Document): Document { const doc = new Document(prototype.title, prototype.content); doc.metadata = new Map(prototype.metadata); doc.createdAt = new Date(prototype.createdAt); return doc; }
clone(): Document { return Document.fromPrototype(this); }
addMetadata(key: string, value: any): void { this.metadata.set(key, value); }}
// Usageconst template = new Document("Template", "Template content");template.addMetadata("author", "System");
const doc1 = template.clone();doc1.title = "Document 1";doc1.addMetadata("editor", "User 1");
const doc2 = template.clone();doc2.title = "Document 2";doc2.addMetadata("editor", "User 2");class Node { value: number; next: Node | null = null; visited: boolean = false;
constructor(value: number) { this.value = value; }
// Deep clone with circular reference handling clone(visited: Map<Node, Node> = new Map()): Node { // Check if already cloned if (visited.has(this)) { return visited.get(this)!; }
// Create new node const cloned = new Node(this.value); visited.set(this, cloned);
// Clone next node if exists if (this.next) { cloned.next = this.next.clone(visited); }
return cloned; }}
// Usage with circular referenceconst node1 = new Node(1);const node2 = new Node(2);const node3 = new Node(3);
node1.next = node2;node2.next = node3;node3.next = node1; // Circular reference
const clonedNode1 = node1.clone();// Successfully clones the entire circular structure