Pular para o conteúdo

Atividade 04: Prototype

Para cada cenário abaixo, indique se o padrão Prototype é apropriado ou não e justifique em 2–3 frases.

  1. Um sistema de relatórios financeiros em que os relatórios mensais nascem de um template base com cabeçalho, rodapé e seções padrão, mudando apenas os valores de cada mês.
  2. Uma classe simples Ponto com apenas dois campos obrigatórios (x, y), utilizada em um sistema de CAD e instanciada dezenas de vezes por segundo.
  3. Um sistema de jogos em que os inimigos possuem muita configuração (estatísticas, equipamentos, habilidades) e várias fases criam variações de um mesmo guerreiro repetindo o mesmo código de construção.
  4. Um módulo em que o cliente não deve conhecer as classes concretas dos objetos que precisa, podendo apenas pedir uma cópia pelo nome do modelo (por exemplo, "guerreiro", "mago") através de um registro.
  5. Uma classe Produto com três campos obrigatórios (nome, preco, quantidadeEstoque), criada em um único ponto do sistema através do construtor tradicional e sem variações.

Para cada item, responda:

  • “Faz sentido usar Prototype” ou “Não faz sentido usar Prototype”.
  • Explique rapidamente o porquê (custo de criação, repetição de configuração, variações sobre uma base, acoplamento, overengineering, etc.).

Crie uma analogia própria para explicar o padrão Prototype para alguém que não é da área de TI.

Na aula, usamos a mitose (uma célula origina outra semelhante, o modelo participa da criação da cópia e a nova versão nasce pronta para uso). Agora crie uma outra analogia.

Descreva uma situação do mundo real em que:

  • um objeto já pronto sirva de modelo para gerar cópias,
  • as cópias nasçam completas e prontas para uso (sem reconstruir do zero),
  • e a partir da mesma base seja possível gerar variações mudando apenas alguns detalhes.

Considere o código Java abaixo, usado em um jogo para criar os inimigos de cada fase:

public class Fase {
private Inimigo criarCopia(Inimigo antigo) {
Inimigo copia = new Inimigo();
copia.setTipo(antigo.getTipo());
copia.setVida(antigo.getVida());
copia.setDano(antigo.getDano());
copia.setArma(antigo.getArma());
return copia;
}
}
public class Inimigo {
private String tipo;
private Double vida;
private Double dano;
private Arma arma; // Arma é um objeto mutável com nome e bônus de dano
// getters e setters...
}

Responda:

  1. Por que essa forma de copiar campo a campo é um problema de design?
  2. Que tipo de bugs ou comportamentos estranhos podem acontecer se um novo campo for adicionado a Inimigo e o criarCopia não for atualizado?
  3. Observe copia.setArma(antigo.getArma()). O que acontece se o clone compartilhar a mesma instância de Arma do original e o jogo modificar a arma de um deles? Qual conceito visto em aula isso ilustra (cópia rasa × profunda)?

Acesse os seguintes arquivos no projeto open source do JDK:

O Java traz suporte a clonagem nativa: Object.clone() cria uma cópia do objeto, mas só funciona se a classe implementar a interface marcadora Cloneable. Diferente do padrão visto em aula, aqui a própria máquina virtual é quem faz a cópia.

Responda:

  1. Observe as exigências do clone() nativo: retorna Object (exigindo cast), pode lançar CloneNotSupportedException e depende de super.clone(). Compare esse mecanismo com o contrato de clonagem visto em aula (interface com método clonar() + construtor de cópia). Qual é mais próximo da ideia do GoF de Prototype e por quê?
  2. Vimos que a cópia rasa faz o clone compartilhar os objetos internos com o original. No clone() nativo do Java, o comportamento padrão é a cópia rasa ou cópia profunda?

Imagine que você foi contratado para criar o sistema de inimigos de um jogo.

O jogo possui vários tipos de inimigo (guerreiro, mago, arqueiro, chefe), cada um com muita configuração (tipo, vida, dano e uma Arma). Como os inimigos nascem de uma base parecida e cada fase precisa de várias variações, recriar cada um com new repetiria muito código de construção.

Implemente, em Java, um sistema que crie inimigos por cópia de protótipos, usando o padrão Prototype.

  1. Crie a classe Arma:
    • Campos, por exemplo, nome e bonusDano, com getters e setters (objeto mutável).
    • Um método clonar() que retorna uma nova Arma independente (para a cópia profunda).
  2. Crie a interface InimigoPrototype:
    • Contrato de clonagem: InimigoPrototype clonar().
  3. Crie a classe Inimigo implements InimigoPrototype:
    • Campos: tipo, vida, dano e Arma arma.
    • Um construtor de cópia (Inimigo(Inimigo base)) que copia os atributos simples e faz a cópia profunda da Arma (usando arma.clonar()).
    • Um método clonar() que retorna new Inimigo(this).
    • Getters e setters para ajustar variações após a cópia.
  4. Crie a classe RegistroDePrototipos:
    • Um Map<String, InimigoPrototype> que guarda os protótipos prontos ("guerreiro", "mago", "arqueiro", "chefe").
    • Um método, por exemplo, getPrototipo(String nome), que retorna o clone do protótipo (nunca o próprio protótipo).
    • O cliente pede a cópia pelo nome, sem conhecer a classe concreta Inimigo.
  5. Crie uma classe de teste, por exemplo Main, que:
    • Obtenha inimigos via RegistroDePrototipos pelos nomes.
    • Crie um inimigo elite a partir do "guerreiro" (por exemplo, setVida(...) e setDano(...) maiores) e mostre que o protótipo original não muda.
    • Prove que os clones são objetos distintos (por exemplo, comparando referências ou hashCode()).
    • Demonstre a cópia profunda: modifique a Arma de um clone e mostre que a Arma de outro clone (e do protótipo) permanece intacta.
    • Se quiser, mostre também o que aconteceria se a cópia da Arma fosse rasa (compartilhando a mesma instância).