A massa já temperada
Um confeiteiro não recomeça a receita a cada biscoito: prepara uma massa-base temperada e corta porções dela. Cada porção nasce com todo o trabalho já feito e pode ser decorada de um jeito diferente. O que ninguém faz é decorar a massa-base — se ela for alterada, todos os biscoitos seguintes saem errados.
O problema
Construção cara acontece mais do que parece: objetos que leem configuração, aplicam padrões corporativos ou derivam dezenas de campos a partir de poucos.
E há o caso em que simplesmente não existe construtor a chamar: você recebeu algo tipado pela interface e precisa de uma cópia independente.
O segundo caso é o mais estrutural. Sem o padrão, copiar exige descobrir a classe concreta — o que devolve ao código o conhecimento que a interface existia para esconder. Delegar a cópia ao objeto resolve isso do mesmo modo que o polimorfismo resolve qualquer outra decisão por tipo.
Vale distinguir dos vizinhos criacionais. Builder monta um objeto complexo passo a passo, partindo do nada, e é ideal quando cada instância difere bastante. Abstract Factory cria famílias coerentes de objetos relacionados. Prototype parte de algo que já existe e quer variações próximas dele.
Em JavaScript há uma coincidência de nomes que confunde: a linguagem tem herança prototipal, em que objetos herdam de outros objetos. É um mecanismo da linguagem, não este padrão — embora Object.create e structuredClone sejam ferramentas úteis para implementá-lo.
Cópia rasa × profunda
Toda a dificuldade do padrão está numa pergunta: ao copiar, o que deve ser duplicado e o que pode continuar compartilhado?
A cópia rasa duplica o objeto e mantém as referências internas apontando para os mesmos alvos. É barata e correta quando o que está dentro é imutável ou deliberadamente compartilhado.
A cópia profunda duplica recursivamente todo o grafo. É o que a intuição espera de 'clone', e é onde os bugs moram: ciclos causam recursão infinita, recursos externos (conexões, handles de arquivo) não podem ser duplicados, e objetos grandes ficam caros.
A escolha honesta é caso a caso, e é justamente por isso que o padrão coloca clonar dentro do objeto: só ele sabe que a lista de blocos precisa ser duplicada, que a configuração é um valor e que a conexão com o banco deve ser reaproveitada. Um clonador genérico externo nunca teria essa informação.
Com Prototype: copiar um modelo
- o trabalho de configuração acontece uma vez
- criar é clonar o modelo certo do catálogo
- o próprio objeto copia o que é privado
- variantes são registros no catálogo, não classes
O custo é a disciplina da cópia: decidir raso ou profundo por campo, e proteger o modelo de ser modificado por quem recebeu um clone.
Registro de modelosguarda os protótipos, nunca os entrega
Modelo 'contrato'já configurado
Modelo 'proposta'já configurado
Documento (clone)cópia independente entregue a quem pediu
- 1
Preparar o modelo
Monte uma vez o objeto já configurado (o protótipo) e trate-o como somente-leitura.
- 2
Implementar clonar()
O próprio objeto decide o que duplicar (cópia profunda nos mutáveis) e o que compartilhar.
- 3
Registrar no catálogo
Guarde os modelos por nome; o registro nunca devolve o original — só clones.
- 4
Criar pedindo cópia
Quem precisa de uma instância chama criar(nome) e edita a cópia, sem tocar no modelo.
Navegue pelas camadas
Camada 1/3Protótipo
use ← →Camada 1
Protótipo
o modelo já pronto
Uma instância configurada que serve de base. Deve ser tratada como somente-leitura pelo sistema inteiro: quem precisa alterar, clona antes.
No código
new Documento('Contrato padrão', ['cláusula 1'], { tema: 'formal' })Se violar: alterar o protótipo em vez do clone contamina todas as criações futuras — e o bug aparece em objetos que ninguém tocou.
Estrutura
Código
interface Clonavel<T> {
clonar(): T;
}
class Documento implements Clonavel<Documento> {
constructor(
public titulo: string,
public blocos: string[],
public config: { tema: string; margens: number },
) {}
clonar(): Documento {
// copia PROFUNDA: sem isso, o clone compartilha o array e o objeto
return new Documento(
this.titulo,
[...this.blocos],
{ ...this.config },
);
}
}
// Registro de modelos prontos
class Registro {
private modelos = new Map<string, Documento>();
registrar(nome: string, p: Documento): void {
this.modelos.set(nome, p);
}
criar(nome: string): Documento {
const p = this.modelos.get(nome);
if (!p) throw new Error("modelo desconhecido: " + nome);
return p.clonar(); // o modelo nunca sai do registro
}
}
const registro = new Registro();
registro.registrar(
"contrato",
new Documento("Contrato padrão", ["cláusula 1", "cláusula 2"], { tema: "formal", margens: 3 }),
);
const meu = registro.criar("contrato");
meu.blocos.push("cláusula extra");
console.log(registro.criar("contrato").blocos.length); // 2 — o modelo segue intactoimport copy
class Documento:
def __init__(self, titulo, blocos, config):
self.titulo, self.blocos, self.config = titulo, blocos, config
def clonar(self):
# copia PROFUNDA: sem isso o clone compartilha a lista e o dict
return copy.deepcopy(self)
class Registro:
def __init__(self):
self._modelos = {}
def registrar(self, nome, p):
self._modelos[nome] = p
def criar(self, nome):
if nome not in self._modelos:
raise KeyError("modelo desconhecido: " + nome)
return self._modelos[nome].clonar() # o modelo nunca sai
registro = Registro()
registro.registrar(
"contrato",
Documento("Contrato padrao", ["clausula 1", "clausula 2"], {"tema": "formal"}),
)
meu = registro.criar("contrato")
meu.blocos.append("clausula extra")
print(len(registro.criar("contrato").blocos)) # 2 — o modelo segue intactoCasos de uso
Modelos de documento em editor
Cenário
Um editor oferece modelos de contrato, proposta e relatório, cada um com estrutura, estilos e cláusulas padrão já montados.
Como o padrão entra
Cada modelo é um protótipo no registro. 'Novo a partir de modelo' clona o escolhido, e o usuário edita a cópia sem risco para o original.
Trade-off: Atualizar um modelo não afeta os documentos já criados a partir dele — o que é desejável na maioria das vezes e frustrante quando a empresa muda uma cláusula obrigatória e precisa propagar.
Entidades pré-configuradas em testes
Cenário
Uma suíte precisa de centenas de clientes válidos, variando um campo por vez, e montar cada um do zero deixa os testes verbosos.
Como o padrão entra
Um protótipo de cliente válido é clonado e ajustado em cada teste. O teste passa a mostrar apenas o que é relevante para ele, e o resto vem pronto.
Trade-off: Se o protótipo for compartilhado entre testes sem clonagem correta, um teste altera o objeto e contamina os seguintes — produzindo falhas que dependem da ordem de execução e somem quando você roda o teste isolado.
Duplicar elementos num editor gráfico
Cenário
Um usuário seleciona uma forma qualquer — retângulo, grupo, texto — e pede 'duplicar'; o editor não sabe de antemão qual tipo recebeu.
Como o padrão entra
Toda forma implementa clonar(). O comando de duplicar chama o método pela interface comum, e cada tipo cuida da própria cópia, inclusive de filhos aninhados.
Trade-off: Grupos aninhados exigem cópia profunda cuidadosa; e formas que referenciam recursos externos (uma imagem carregada) precisam decidir se duplicam o recurso ou compartilham — decisão que muda o consumo de memória do editor.
O clone que compartilha o miolo
Compila, roda, passa no code review. E quebra depois.
Existe clonar(), o registro devolve 'cópias', tudo parece certo — mas listas e objetos internos continuam sendo a mesma referência. Alterar o clone altera o modelo.
class Documento {
constructor(
public titulo: string,
public blocos: string[],
public config: { tema: string },
) {}
// "Clone" que so copia a casca — listas e objetos ficam compartilhados.
clonar(): Documento {
return new Documento(this.titulo, this.blocos, this.config);
}
}
const modelo = new Documento("Contrato", ["clausula 1"], { tema: "formal" });
const copia = modelo.clonar();
copia.blocos.push("clausula extra");
copia.config.tema = "informal";
// O modelo "protegido" tambem mudou.
console.log(modelo.blocos); // ["clausula 1", "clausula extra"]
console.log(modelo.config.tema); // "informal"O que quebra
- No teste seguinteO modelo 'limpo' já veio contaminado pelo teste anterior — falhas que dependem da ordem.
- Em produçãoUm usuário edita o documento e o template padrão muda para todo mundo.
- Ao depurarNinguém tocou no modelo; mesmo assim ele mudou — a referência compartilhada esconde a causa.
A saída: Clone profundo no que é mutável ([...blocos], { ...config }, ou structuredClone). O teste que importa: clonar, alterar profundamente a cópia e afirmar que o original não mudou.
Armadilhas
Cópia rasa disfarçada de clone
É o bug número um do padrão.
{ ...this }copia o objeto mas mantém listas e objetos internos compartilhados; alterar o clone altera o original de forma invisível. O teste que pega isso é clonar, modificar profundamente o clone e verificar que o original não mudou.Protótipo mutável no registro
Se qualquer código conseguir uma referência ao protótipo e alterá-lo, todas as criações seguintes saem contaminadas — e a causa fica muito distante do sintoma. O registro deve entregar apenas clones, e o protótipo idealmente ser imutável ou congelado.
Clonar o que não deve ser clonado
Uma cópia profunda ingênua tenta duplicar conexões de banco, sockets, caches e até o container de injeção de dependências. O resultado vai de erro imediato a vazamento silencioso de recursos. Cada classe precisa dizer explicitamente o que se copia e o que se reaproveita.
Quando usar × quando evitar
Use quando
3- Montar o objeto é caro e a configuração se repete.
- Você precisa copiar conhecendo apenas a interface.
- Existem variantes pré-configuradas que cabem num catálogo.
Evite quando
3- O objeto é simples de construir.
- O objeto é imutável — compartilhar já é seguro.
- O grafo interno tem ciclos ou recursos não copiáveis.
Testar o que ficou
3 armadilhas de Prototype viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.
Onde isto aparece de verdade
Você já usa isto, talvez sem saber o nome.
Object.createCria um objeto novo a partir de outro que serve de molde — é o padrão como chamada de linguagem.structuredCloneCópia profunda de um objeto existente, sem precisar conhecer a classe dele.A cadeia de protótipos do JSHerança em JavaScript é clonagem de protótipo, não instanciação de classe — a classe é açúcar por cima.
O padrão em uma linha
Sem nome de domínio, sem cerimônia. Só a ideia.
// Clona em vez de reconfigurar do zero.
const copia = structuredClone(modelo);O que ele cobra
1 salto a mais entre a chamada e o efeito.
- Cada classe precisa saber se clonar, incluindo a decisão entre cópia rasa e profunda
- Clone mal feito compartilha referências sem querer e vira bug de estado partilhado
Não vale se: criar do zero é barato e claro — clonar só ganha quando a construção é cara ou o estado a copiar é grande.
Onde isto aparece
Continue por aqui
- BuilderConstrói objetos complexos passo a passo por uma interface fluente, separando a montagem (incremental, com escolhas opcionais) do objeto final (completo e validado).
- Abstract FactoryCria famílias inteiras de objetos relacionados através de uma interface única, garantindo que os produtos de uma mesma variante sempre combinem entre si.
- MementoCaptura o estado interno de um objeto num instantâneo opaco que pode ser guardado e restaurado depois, sem expor os detalhes desse estado a quem o guarda.