Prototype
Introdução
Seção intitulada “Introdução”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.
Problema
Seção intitulada “Problema”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.
Solução
Seção intitulada “Solução”O Prototype resolve esse problema transferindo a responsabilidade da cópia para o próprio objeto.
- Define-se um método de clonagem, como
clonar(). - Cada objeto sabe como criar uma cópia de si mesmo.
- O cliente pede a cópia sem depender da classe concreta.
- Objetos já configurados podem ser guardados como protótipos reutilizáveis.
- 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”.
Analogia
Seção intitulada “Analogia”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.
Aplicabilidade
Seção intitulada “Aplicabilidade”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
Anti-padrão / Mau uso
Seção intitulada “Anti-padrão / Mau uso”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.
Como implementar
Seção intitulada “Como implementar”Uma implementação didática pode seguir estes passos:
- Identifique objetos que são recriados com configurações muito parecidas.
- Defina uma interface ou método comum de clonagem.
- Implemente a cópia dos atributos dentro da própria classe.
- Decida quais campos podem ser compartilhados e quais precisam de cópia profunda.
- Crie protótipos base já configurados.
- 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.
Exemplo em código
Seção intitulada “Exemplo em código”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
Contras
Seção intitulada “Contras”- 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
Relações com outros padrões/conceitos
Seção intitulada “Relações com outros padrões/conceitos”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
Implementações alternativas (quando aplicável)
Seção intitulada “Implementações alternativas (quando aplicável)”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.
Exemplo completo
Seção intitulada “Exemplo completo”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.