O padrão de design Prototype tem como objetivo principal permitir a criação de novos objetos por meio da cópia de um objeto existente, conhecido como protótipo.

Este padrão é usado para resolver problemas específicos relacionados à criação de objetos, especialmente em ==situações onde a instanciação direta (usando o operador new) não é a opção mais eficiente ou adequada==. Os problemas que o padrão Prototype busca resolver incluem:

  1. Criação de Objetos Custosa: Quando a criação de um novo objeto é mais custosa em termos de recursos e tempo (por exemplo, quando requer uma série de operações, como leitura de dados de uma base de dados, operações de rede ou processamento intensivo), o Prototype oferece uma alternativa eficiente, permitindo a criação de um objeto a partir de uma cópia de um objeto existente.

  2. Evitar Subclasses de Fábrica: Em vez de usar uma hierarquia de classes de fábrica ou um construtor complexo, o Prototype permite criar variantes de objetos por meio da clonagem de um protótipo pré-configurado.

  3. Preservação do Estado Interno: Em algumas situações, é necessário que um objeto seja criado com um estado que corresponda ao de um objeto existente. O Prototype permite clonar o estado interno do objeto existente, evitando expor e duplicar a lógica de inicialização.

  4. Abstração do Código de Criação: Ao utilizar o padrão Prototype, o cliente pode ser abstraído do processo de criação dos objetos. O cliente precisa conhecer apenas o protótipo e o mecanismo de clonagem.

  5. Flexibilidade em Adicionar ou Remover Objetos em Tempo de Execução: Com o Prototype, é possível adicionar ou remover protótipos em tempo de execução, aumentando a flexibilidade do sistema.

  6. Instanciação de Classes Dinâmicas: Quando as classes de objetos que precisam ser criados são determinadas em tempo de execução, como em sistemas que precisam de extensibilidade ou carga de módulos dinâmicos.

Em resumo, o padrão Prototype é uma solução para situações em que a criação direta de objetos é inadequada ou ineficiente. Ele oferece um meio de criar objetos por meio da clonagem, o que pode ser especialmente útil em sistemas complexos onde os custos de criação de objetos são altos ou a flexibilidade na criação de novas instâncias é uma exigência chave.

Exemplo de implementação

  1. Definir uma interface com método clone
  2. Criar uma classe que implementa a interface.
    1. Na implementação do método clone retornar a instanciação de um novo objeto com os parâmetros do construtor preenchidos.

Definindo a interface Prototype

public interface Prototype {
    Prototype clone();
}
 

Implementando a interface em uma classe concreta

public class Carro implements Prototype {
    private String marca;
    private String modelo;
 
    public Carro(String marca, String modelo) {
        this.marca = marca;
        this.modelo = modelo;
    }
 
    // Métodos getters e setters
 
    @Override
    public Prototype clone() {
        return new Carro(marca, modelo);
    }
 
    @Override
    public String toString() {
        return "Carro{" + "marca='" + marca + '\'' + ", modelo='" + modelo + '\'' + '}';
    }
}
 

Utilizando o Prototype para clonar objetos

public class Main {
    public static void main(String[] args) {
        Carro carroOriginal = new Carro("Toyota", "Corolla");
        System.out.println("Carro Original: " + carroOriginal);
 
        Carro carroClonado = (Carro) carroOriginal.clone();
        System.out.println("Carro Clonado: " + carroClonado);
    }
}
 

Diferença entre Shallow e Depp copy

A diferença entre cópias superficiais (“shallow copy”) e profundas (“deep copy”) é um conceito importante no padrão Prototype, assim como em outras áreas de programação onde a duplicação de objetos é necessária.

Shallow Copy (Cópia Superficial)

  • Definição: Uma cópia superficial de um objeto copia todos os campos do objeto original para o novo objeto. Se os campos são valores primitivos, uma cópia direta dos valores é feita. Se os campos são referências a outros objetos, então as referências são copiadas, e não os objetos em si.
  • Consequência: Após uma cópia superficial, os dois objetos (original e cópia) compartilharão as mesmas referências para objetos que não são primitivos. Qualquer modificação nesses objetos referenciados afetará ambos os objetos, o que pode ser indesejado em muitos cenários.

Deep Copy (Cópia Profunda)

  • Definição: Uma cópia profunda copia todos os campos, e se um campo é uma referência a outro objeto, ele também copia o objeto referenciado e assim por diante. Isso resulta na duplicação completa não apenas do objeto original, mas também de todos os objetos aos quais ele se refere diretamente ou indiretamente.
  • Consequência: Após uma cópia profunda, os dois objetos são completamente independentes. Modificar um objeto não afetará o outro. Isso é útil quando você deseja trabalhar com uma cópia verdadeiramente independente do objeto original.

Exemplo em Java

Vamos considerar um exemplo em Java para ilustrar esses conceitos:

class ObjetoReferenciado {
    public int valor;
 
    public ObjetoReferenciado(int valor) {
        this.valor = valor;
    }
}
 
class MeuObjeto implements Cloneable {
    public int valorPrimitivo;
    public ObjetoReferenciado objetoReferenciado;
 
    public MeuObjeto(int valorPrimitivo, int valorReferenciado) {
        this.valorPrimitivo = valorPrimitivo;
        this.objetoReferenciado = new ObjetoReferenciado(valorReferenciado);
    }
 
    // Método para realizar uma cópia superficial
    public MeuObjeto shallowCopy() throws CloneNotSupportedException {
        return (MeuObjeto) this.clone();
    }
 
    // Método para realizar uma cópia profunda
    public MeuObjeto deepCopy() {
        MeuObjeto copia = new MeuObjeto(this.valorPrimitivo, this.objetoReferenciado.valor);
        return copia;
    }
}

Neste exemplo, shallowCopy usa ==o método clone de Java, que por padrão realiza uma cópia superficial==. Isso significa que tanto o objeto original quanto a cópia compartilharão a mesma referência objetoReferenciado. Por outro lado, deepCopy cria uma nova instância de ObjetoReferenciado, garantindo que o objeto copiado tenha suas próprias cópias independentes de todos os objetos referenciados.

Escolher entre cópia superficial e profunda depende das necessidades específicas do seu programa e do comportamento que você deseja para os objetos copiados.