Pular para o conteúdo

O Prototype é um padrão de projeto criacional que permite criar novos objetos a partir da cópia de um objeto já existente. Em vez de reconstruir tudo do zero, o sistema usa um objeto-modelo como base e gera novas versões a partir dele.

Esse padrão é útil quando a criação de um objeto exige muitas configurações, possui várias combinações possíveis ou depende de detalhes internos que o código cliente não deveria conhecer. Ele existe para reduzir repetição, diminuir acoplamento e tornar a criação de objetos mais flexível.

Imagine um sistema de jogos que precisa criar personagens inimigos com muitas configurações: nome, vida, velocidade, armas, aparência e comportamento. Montar cada instância manualmente pode funcionar no começo, mas logo surgem problemas práticos:

  • muito código repetido para configurar objetos parecidos
  • risco de esquecer algum atributo importante na criação
  • alto acoplamento entre o cliente e a classe concreta
  • dificuldade para copiar objetos complexos com segurança
  • necessidade de reconstruir objetos que já existem prontos como modelo

Além disso, em alguns cenários o cliente conhece apenas uma interface, e não a classe concreta do objeto que precisa duplicar. Nesse caso, usar new diretamente deixa de ser uma boa solução.

O Prototype resolve esse problema transferindo a responsabilidade da cópia para o próprio objeto.

  1. Define-se um método de clonagem, como clonar().
  2. Cada objeto sabe como criar uma cópia de si mesmo.
  3. O cliente pede a cópia sem depender da classe concreta.
  4. Objetos já configurados podem ser guardados como protótipos reutilizáveis.
  5. Quando necessário, um registro de protótipos permite localizar modelos prontos e cloná-los sob demanda.

Assim, o foco deixa de ser “como construir tudo novamente” e passa a ser “qual modelo existente deve ser duplicado”.

Pense em um formulário padrão da universidade para abertura de turma. Em vez de criar um documento novo a cada semestre, a secretaria faz uma cópia do modelo já pronto e altera apenas o que muda, como período, sala e professor.

O documento original funciona como um protótipo: ele serve de base para novas versões, economizando tempo e evitando erros de preenchimento.

Use Prototype quando:

  • vários objetos são muito parecidos entre si
  • a criação de objetos é cara, trabalhosa ou cheia de etapas
  • você quer reduzir dependência do cliente em relação às classes concretas
  • faz sentido manter modelos pré-configurados para reutilização
  • o sistema precisa gerar muitas variações a partir de uma base comum

Exemplos reais em software moderno:

  • criação de inimigos, itens ou fases em jogos
  • templates de notificações em aplicativos e serviços online
  • perfis de configuração em sistemas web
  • modelos de cursos, turmas ou avaliações em sistemas acadêmicos
  • dashboards e componentes clonados a partir de layouts prontos

Prototype pode ser mal utilizado quando vira solução para qualquer criação de objeto, mesmo quando um construtor simples já resolveria. Os problemas mais comuns são:

  • usar clonagem sem necessidade real
  • misturar cópia rasa e cópia profunda sem critério
  • compartilhar estado mutável entre original e cópia sem perceber
  • esconder regras de negócio importantes dentro do processo de clonagem

Na prática, isso pode gerar bugs difíceis de rastrear, objetos inconsistentes e comportamento inesperado quando uma alteração em um objeto afeta outro que deveria ser independente.

Uma implementação didática pode seguir estes passos:

  1. Identifique objetos que são recriados com configurações muito parecidas.
  2. Defina uma interface ou método comum de clonagem.
  3. Implemente a cópia dos atributos dentro da própria classe.
  4. Decida quais campos podem ser compartilhados e quais precisam de cópia profunda.
  5. Crie protótipos base já configurados.
  6. Se necessário, mantenha um registro para buscar protótipos por nome ou categoria.

Antes de codificar, vale responder a uma pergunta essencial: o clone deve compartilhar objetos internos ou deve duplicá-los também? Essa decisão muda bastante o resultado final.

No exemplo a seguir, um jogo usa um inimigo base como protótipo para criar variações já configuradas.

import java.util.ArrayList;
import java.util.List;
class Arma {
private String nome;
public Arma(String nome) {
this.nome = nome;
}
// Construtor de cópia
public Arma(Arma outra) {
this.nome = outra.nome;
}
@Override
public String toString() {
return nome;
}
}
interface InimigoPrototype {
InimigoPrototype clonar();
}
class Inimigo implements InimigoPrototype {
private String tipo;
private int vida;
private double velocidade;
private List<Arma> armas;
public Inimigo(String tipo, int vida, double velocidade, List<Arma> armas) {
this.tipo = tipo;
this.vida = vida;
this.velocidade = velocidade;
this.armas = armas;
}
// Construtor de cópia com cópia profunda da lista de armas
public Inimigo(Inimigo outro) {
this.tipo = outro.tipo;
this.vida = outro.vida;
this.velocidade = outro.velocidade;
this.armas = new ArrayList<>();
for (Arma arma : outro.armas) {
this.armas.add(new Arma(arma));
}
}
@Override
public InimigoPrototype clonar() {
return new Inimigo(this);
}
public void setVida(int vida) {
this.vida = vida;
}
public void exibir() {
System.out.println(tipo + " | vida=" + vida + " | velocidade=" + velocidade + " | armas=" + armas);
}
}
public class Jogo {
public static void main(String[] args) {
List<Arma> armasBase = new ArrayList<>();
armasBase.add(new Arma("Espada"));
Inimigo guerreiroBase = new Inimigo("Guerreiro", 100, 2.5, armasBase);
Inimigo guerreiroElite = (Inimigo) guerreiroBase.clonar();
guerreiroElite.setVida(150);
guerreiroBase.exibir();
guerreiroElite.exibir();
}
}

Nesse código, Inimigo sabe como criar sua própria cópia. O cliente não precisa repetir toda a configuração do objeto. Além disso, a lista de armas é duplicada item a item, evitando compartilhamento acidental de estado mutável.

  • reduz código repetido na criação de objetos
  • diminui o acoplamento com classes concretas
  • facilita gerar variações a partir de modelos prontos
  • pode melhorar desempenho quando recriar um objeto é mais caro do que copiá-lo
  • ajuda em cenários com muitas configurações pré-definidas
  • implementar clonagem correta pode ser difícil
  • cópia rasa e cópia profunda exigem cuidado extra
  • objetos com referências circulares tornam a solução mais complexa
  • o padrão pode deixar o modelo mais difícil de entender se for usado sem necessidade

O Prototype se conecta com outros padrões importantes:

  • Factory Method: ambos encapsulam criação, mas Factory Method escolhe qual objeto criar, enquanto Prototype cria por cópia
  • Abstract Factory: pode usar protótipos para montar famílias de objetos relacionados
  • Builder: Builder constrói passo a passo; Prototype parte de um objeto já pronto
  • Memento: em alguns cenários, uma cópia do objeto pode servir para guardar estado
  • Composite e Decorator: estruturas complexas podem ser clonadas sem precisar ser reconstruídas manualmente

Há diferentes formas de implementar Prototype:

  • método explícito como clonar()
  • construtor de cópia
  • registro de protótipos com Map<String, Prototype>
  • serialização e desserialização para cópia profunda em cenários específicos

Em Java, também existe Cloneable com Object.clone(), mas muitas equipes preferem construtores de cópia ou métodos explícitos, porque deixam a intenção mais clara e o controle maior.

Agora veja um caso mais realista em um sistema acadêmico. A universidade mantém modelos de cursos já configurados e cria ofertas semestrais a partir desses protótipos.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class PlanoAvaliacao {
private List<String> componentes;
public PlanoAvaliacao(List<String> componentes) {
this.componentes = componentes;
}
public PlanoAvaliacao(PlanoAvaliacao outro) {
this.componentes = new ArrayList<>(outro.componentes);
}
@Override
public String toString() {
return componentes.toString();
}
}
interface CursoPrototype {
CursoPrototype clonar();
}
class Curso implements CursoPrototype {
private String nome;
private int cargaHoraria;
private String ambienteVirtual;
private PlanoAvaliacao planoAvaliacao;
public Curso(String nome, int cargaHoraria, String ambienteVirtual, PlanoAvaliacao planoAvaliacao) {
this.nome = nome;
this.cargaHoraria = cargaHoraria;
this.ambienteVirtual = ambienteVirtual;
this.planoAvaliacao = planoAvaliacao;
}
public Curso(Curso outro) {
this.nome = outro.nome;
this.cargaHoraria = outro.cargaHoraria;
this.ambienteVirtual = outro.ambienteVirtual;
this.planoAvaliacao = new PlanoAvaliacao(outro.planoAvaliacao);
}
@Override
public CursoPrototype clonar() {
return new Curso(this);
}
public void definirAmbienteVirtual(String ambienteVirtual) {
this.ambienteVirtual = ambienteVirtual;
}
public void exibir() {
System.out.println(nome + " | carga=" + cargaHoraria + "h | AVA=" + ambienteVirtual + " | avaliação=" + planoAvaliacao);
}
}
class RegistroCursos {
private Map<String, CursoPrototype> prototipos = new HashMap<>();
public void registrar(String chave, CursoPrototype prototipo) {
prototipos.put(chave, prototipo);
}
public CursoPrototype obterClone(String chave) {
CursoPrototype prototipo = prototipos.get(chave);
if (prototipo == null) {
throw new IllegalArgumentException("Protótipo não encontrado: " + chave);
}
return prototipo.clonar();
}
}
public class UniversidadeApp {
public static void main(String[] args) {
RegistroCursos registro = new RegistroCursos();
Curso engenhariaSoftware = new Curso(
"Engenharia de Software",
80,
"Moodle padrão",
new PlanoAvaliacao(List.of("Prova", "Projeto", "Seminário"))
);
registro.registrar("eng-soft", engenhariaSoftware);
Curso turma2026 = (Curso) registro.obterClone("eng-soft");
turma2026.definirAmbienteVirtual("Moodle 2026.1");
Curso turma2027 = (Curso) registro.obterClone("eng-soft");
turma2027.definirAmbienteVirtual("Moodle 2027.1");
engenhariaSoftware.exibir();
turma2026.exibir();
turma2027.exibir();
}
}

Nesse exemplo, RegistroCursos funciona como um catálogo de protótipos. O curso base é configurado uma única vez, e cada nova turma nasce como cópia desse modelo. Isso reduz repetição, melhora a consistência das ofertas e deixa o cliente focado no uso do objeto, não na sua montagem.