CriacionalIntermediário6 min de leitura9 seções1994, Marco de referência: a ideia é mais velha que a data

Prototype

Cria objetos novos copiando um modelo já configurado, em vez de construí-los do zero — útil quando montar a configuração é caro ou quando o tipo concreto não deve ser conhecido.

Em 10 segundos

Em vez de construir do zero, você parte de um modelo pronto e pede a ele uma cópia. Como quem clona é o próprio objeto, ele copia até o que é privado — e quem chama não precisa conhecer a classe concreta.

Antes de ter nome

Self (1987) construiu a linguagem inteira em cima de clonagem de protótipos, sem classe nenhuma — e o JavaScript herdou isso.

  • #clonagem
  • #copia
  • #modelo
  • #gof
Analogia

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.

01Conceito

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.

02Conceito

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.

Sem o padrão × com o padrã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.

O ganho é deslocar a complexidade de 'como montar' para 'o que copiar'. Isso compensa quando a montagem é cara e as instâncias se parecem muito entre si.
Como se organiza
  • 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

O modelo fica dentro do registro e só sai como cópia. É essa fronteira que impede alguém de alterar o protótipo e contaminar todas as criações seguintes.
Passo a passoO fluxo, passo a passo
  1. 1

    Preparar o modelo

    Monte uma vez o objeto já configurado (o protótipo) e trate-o como somente-leitura.

  2. 2

    Implementar clonar()

    O próprio objeto decide o que duplicar (cópia profunda nos mutáveis) e o que compartilhar.

  3. 3

    Registrar no catálogo

    Guarde os modelos por nome; o registro nunca devolve o original — só clones.

  4. 4

    Criar pedindo cópia

    Quem precisa de uma instância chama criar(nome) e edita a cópia, sem tocar no modelo.

03Anatomia

Navegue pelas camadas

Camada 1/3Protótipo

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.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

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 intacto
06No mundo real

Casos 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.

08Cuidado

Armadilhas

  1. 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.

  2. 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.

  3. 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.

09Decisão

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