Prototype Pattern para Clonacion de Objetos y Plantillas
Crea nuevos objetos copiando existentes, permitiendo plantillas pre-configuradas y evitando explosion de subclases cuando la creacion de objetos es costosa
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Prototype Pattern para Clonacion de Objetos y Plantillas de Configuracion
El Prototype pattern crea nuevos objetos copiando existentes. En lugar de construir objetos desde cero con constructores, clonas un prototipo y opcionalmente lo customizas. Esto es capaz cuando la inicializacion de objetos es costosa, cuando existen muchas configuraciones similares, o cuando el tipo concreto de objeto no se conoce hasta runtime.
Cuando Usar Esto
- La creacion de objetos es costosa (conexiones a base de datos, configuraciones parseadas). Consulta Factory Pattern para patrones de creación.
- Existen muchas variantes de objetos similares que difieren solo ligeramente. Consulta Builder Pattern para objetos configurables.
- La clase concreta a instanciar se determina en runtime. Consulta Strategy Pattern para selección en runtime.
Problema
Un juego spawnea cientos de unidades enemigas con las mismas stats base pero variaciones leves. Crear cada unidad desde cero requiere recargar assets y parsear configuraciones repetidamente.
Solucion
// prototype/Cloneable.ts
interface Cloneable<T> {
clone(): T;
}
class EnemyUnit implements Cloneable<EnemyUnit> {
private health: number;
private speed: number;
private weapon: string;
private abilities: string[];
constructor(
health: number,
speed: number,
weapon: string,
abilities: string[]
) {
this.health = health;
this.speed = speed;
this.weapon = weapon;
// Deep copy para prevenir estado mutable compartido
this.abilities = [...abilities];
}
clone(): EnemyUnit {
return new EnemyUnit(
this.health,
this.speed,
this.weapon,
[...this.abilities]
);
}
setHealth(health: number): EnemyUnit {
this.health = health;
return this;
}
addAbility(ability: string): EnemyUnit {
this.abilities.push(ability);
return this;
}
describe(): string {
return `${this.health}HP, ${this.speed}SPD, ${this.weapon}, [${this.abilities.join(', ')}]`;
}
}
// Prototipos pre-configurados
const goblinPrototype = new EnemyUnit(30, 8, 'dagger', ['sneak']);
const orcPrototype = new EnemyUnit(80, 4, 'axe', ['rage', 'charge']);
// Clona y customiza
const goblinScout = goblinPrototype.clone().setHealth(25).addAbility('scout');
const goblinBoss = goblinPrototype.clone().setHealth(60).addAbility('command');
const orcBerserker = orcPrototype.clone().setHealth(100);
console.log(goblinScout.describe());
console.log(goblinBoss.describe());
console.log(orcBerserker.describe());
Variacion: Registro de Plantillas de Configuracion
// prototype/TemplateRegistry.ts
class DocumentTemplate implements Cloneable<DocumentTemplate> {
private content = '';
private styles: Record<string, string> = {};
private metadata: Record<string, unknown> = {};
constructor() {}
setContent(content: string): DocumentTemplate {
this.content = content;
return this;
}
setStyles(styles: Record<string, string>): DocumentTemplate {
this.styles = { ...styles };
return this;
}
setMetadata(metadata: Record<string, unknown>): DocumentTemplate {
this.metadata = { ...metadata };
return this;
}
clone(): DocumentTemplate {
return new DocumentTemplate()
.setContent(this.content)
.setStyles({ ...this.styles })
.setMetadata({ ...this.metadata });
}
}
class TemplateRegistry {
private templates = new Map<string, DocumentTemplate>();
register(name: string, template: DocumentTemplate): void {
this.templates.set(name, template);
}
create(name: string): DocumentTemplate {
const template = this.templates.get(name);
if (!template) throw new Error(`Unknown template: ${name}`);
return template.clone();
}
}
// Uso
const registry = new TemplateRegistry();
registry.register('report', new DocumentTemplate()
.setContent('## Report\n\nDate: {{date}}')
.setStyles({ font: 'Arial', size: '12pt' }));
registry.register('invoice', new DocumentTemplate()
.setContent('## Invoice #{{id}}\n\nTotal: {{total}}')
.setStyles({ font: 'Times', size: '10pt' }));
const report = registry.create('report');
Como Funciona
- Prototype declara un metodo
clone - Concrete Prototype implementa deep cloning para evitar estado mutable compartido
- Client clona prototipos y opcionalmente customiza la copia
- Registry (opcional) mantiene prototipos nombrados para acceso conveniente
Consideraciones de Produccion
- Siempre deep-clona objetos anidados y arrays para prevenir comparticion accidental
- Usa
structuredCloneen JavaScript moderno para deep copies de objetos plain - Para referencias circulares, implementa logica de clonado custom
Errores Comunes
- Shallow cloning de estado mutable anidado, causando side effects entre instancias
- No implementar
cloneen subclases, rompiendo la cadena del pattern - Usar Prototype cuando un simple constructor o Factory Method es mas limpio
FAQ
P: En que se diferencia de Factory Method? R: Factory Method crea objetos a traves de una clase factory. Prototype crea objetos copiando una instancia existente, preservando su estado.
P: Puedo usar esto con JSON?
R: Si. JSON.parse(JSON.stringify(obj)) es un clone prototype crude para objetos plain, pero structuredClone es preferido para runtimes modernos.
¿Es este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.
Temas Avanzados
Escenario: Clonacion de Configuracion para Multi-tenant
// Prototype pattern para configuracion de tenants
interface TenantConfig {
theme: Theme;
features: string[];
limits: ResourceLimits;
clone(): TenantConfig;
}
class DefaultTenantConfig implements TenantConfig {
constructor(
public theme: Theme,
public features: string[],
public limits: ResourceLimits
) {}
clone(): TenantConfig {
// Deep clone: copia recursiva de objetos anidados
return new DefaultTenantConfig(
{ ...this.theme },
[...this.features],
{ ...this.limits }
);
}
}
// Uso: crear nuevo tenant desde config base
const baseConfig = new DefaultTenantConfig(
{ primary: "#3b82f6", mode: "light" },
["auth", "dashboard", "reports"],
{ maxUsers: 100, maxStorage: 10 }
);
// Clonar y personalizar para cliente premium
const premiumConfig = baseConfig.clone();
premiumConfig.features.push("sso", "audit-log");
premiumConfig.limits.maxUsers = 1000;
premiumConfig.limits.maxStorage = 100;
// Clonar para cliente basico
const basicConfig = baseConfig.clone();
basicConfig.features = ["auth", "dashboard"];
basicConfig.limits.maxUsers = 10;
basicConfig.limits.maxStorage = 1;
// Comparacion: deep clone vs structuredClone vs JSON
| Metodo | Ventajas | Desventajas |
|--------|----------|-------------|
| Manual (spread) | Control total | Tedioso para objetos profundos |
| JSON.parse(JSON.stringify) | Simple | No soporta Date, Map, funciones |
| structuredClone() | Nativo, soporta Date/Map | No soporta funciones |
| lodash _.cloneDeep | Robusto | Dependencia externa |
Lecciones:
- Prototype clona objetos sin acoplarse a su clase concreta
- Deep clone es necesario para objetos anidados
- structuredClone() es nativo en Node.js 17+ y browsers modernos
- Para config multi-tenant, clonar base y personalizar
- Evita JSON.parse/stringify: pierde tipos (Date, Map, undefined)
### Cuando uso structuredClone vs clone manual?
Usa structuredClone() cuando necesitas deep clone de objetos planos con tipos nativos (Date, Map, Set, ArrayBuffer). Usa clone manual cuando necesitas control sobre que clonar (ej: no clonar secrets, reusar referencias compartidas). Usa lodash _.cloneDeep para casos complejos con referencias circulares. Evita JSON.parse/stringify: pierde Date, Map, Set, undefined y funciones.
End of document. Review and update quarterly. Recursos Relacionados
Builder Pattern
Construct complex objects step by step. A creational design pattern for readable, configurable object construction.
PatternAbstract Factory for Cross-Platform UI Component Families
Create families of related objects without specifying concrete classes, enabling platform-specific implementations that share a common interface
PatternMemento Pattern for State Snapshot and Restoration
Capture and externalize an object's internal state without violating encapsulation, enabling undo, serialization, and state rollback in applications