Prototype
Especifica os tipos de objetos a criar usando uma instância prototípica, e cria novos objetos copiando esse protótipo.
Intenção
Criar novos objetos clonando uma instância existente (o protótipo) em vez de construir do zero, permitindo duplicar objetos complexos sem acoplar seu código às classes concretas deles.
Problema
Você precisa criar uma cópia exata de um objeto, mas ele pode ter campos privados ou estado interno complexo que não é acessível de fora. Chamar 'new' e copiar manualmente cada campo é frágil, fortemente acoplado à classe concreta, e quebra quando a classe muda. Alguns objetos são caros de configurar do zero quando já existe um template pré-configurado.
Solução
Declare uma interface comum (ou use um trait da linguagem) com um método clone. Cada classe concreta implementa clone criando uma nova instância de si mesma e copiando seu estado interno — incluindo campos privados. Os clientes pedem ao protótipo que se clone, em vez de instanciar um novo objeto diretamente.
Participantes
- Prototype — declara a interface de clonagem
- ConcretePrototype — implementa a operação de clonagem copiando seu próprio estado interno para uma nova instância
- Client — cria um novo objeto pedindo ao protótipo que se clone
Vantagens
- Clona objetos sem acoplamento às classes concretas — o cliente conhece apenas a interface do protótipo
- Elimina código de inicialização repetido quando templates pré-configurados estão disponíveis
- Produz objetos complexos de forma mais conveniente do que construí-los do zero
- Oferece uma alternativa à subclassificação para variar o tipo de objetos que uma fábrica cria
Desvantagens
- Clonar objetos complexos com referências circulares ou grafos de dependência profundos pode ser complicado
- A semântica de cópia profunda vs. cópia rasa precisa ser decidida e documentada com cuidado
- Linguagens sem um mecanismo de clonagem nativo exigem bastante código repetitivo
Analogia do mundo real
A divisão celular (mitose) é o padrão Prototype da natureza. Em vez de montar uma nova célula a partir de moléculas individuais, uma célula existente duplica todo o seu estado interno — DNA, organelas, membranas — e se divide em duas cópias independentes. Cada cópia pode então sofrer mutações de forma independente, sem afetar a original.
Casos de uso
- Duplicar formas gráficas em um editor de desenho preservando posição, cor e camada
- Criar templates de documentos que os usuários podem clonar e customizar
- Desenvolvimento de jogos: gerar inimigos a partir de um protótipo pré-configurado em vez de reler arquivos de configuração
- Fazer cache de objetos caros de criar e cloná-los sob demanda
- Sistemas de desfazer/refazer que tiram um snapshot e restauram o estado do objeto via clonagem
Exemplos de código
Nota para Java
A interface Cloneable e Object.clone() são amplamente consideradas um erro de design da API do Java -- clone() não chama construtores, contorna checagens de final, e sua semântica de cópia rasa vs. profunda é confusa e mal documentada.
Alternativas: Prefira um copy constructor (como no exemplo) ou um método estático de fábrica copyOf(). Ambos são explícitos sobre o que é copiado, funcionam bem com campos final, e não dependem do contrato frágil de Cloneable.
public abstract class Shape {
private int x;
private int y;
private String color;
protected Shape(int x, int y, String color) {
this.x = x;
this.y = y;
this.color = color;
}
// Copy constructor -- the idiomatic Java alternative to Cloneable
protected Shape(Shape source) {
this(source.x, source.y, source.color);
}
public abstract Shape copy();
public void setX(int x) { this.x = x; }
public int getX() { return x; }
public void setColor(String color) { this.color = color; }
public String getColor() { return color; }
}
final class Circle extends Shape {
private double radius;
Circle(int x, int y, String color, double radius) {
super(x, y, color);
this.radius = radius;
}
private Circle(Circle source) {
super(source);
this.radius = source.radius;
}
@Override
public Circle copy() {
return new Circle(this);
}
}
final class Rectangle extends Shape {
private double width;
private double height;
Rectangle(int x, int y, String color, double width, double height) {
super(x, y, color);
this.width = width;
this.height = height;
}
private Rectangle(Rectangle source) {
super(source);
this.width = source.width;
this.height = source.height;
}
@Override
public Rectangle copy() {
return new Rectangle(this);
}
}
public class PrototypeDemo {
public static void main(String[] args) {
// Usage -- clone and modify independently
Circle original = new Circle(10, 20, "red", 50);
Circle copy = original.copy();
copy.setX(100);
copy.setColor("blue");
System.out.println(original.getX() + " " + original.getColor()); // 10 red
System.out.println(copy.getX() + " " + copy.getColor()); // 100 blue
}
}Clonagem via copy constructor em vez de Cloneable/Object.clone() -- a abordagem idiomática em Java, já que a interface Cloneable é amplamente considerada quebrada (Effective Java a lista como algo a evitar).
Padrões relacionados
Builder
Separa a construção de um objeto complexo da sua representação, para que o mesmo processo de construção possa criar representações diferentes.