Design Patterns
Criacionais
Prototype

Padrão de Projeto: Prototype

Propósito

O Prototype é um padrão de projeto criacional que permite copiar objetos existentes sem fazer seu código ficar dependente de suas classes.

Problema

Digamos que você tenha um objeto, e você quer criar uma cópia exata dele. Como você o faria? Primeiro, você tem que criar um novo objeto da mesma classe. Então você terá que ir por todos os campos do objeto original e copiar seus valores para o novo objeto.

Legal! Mas tem uma pegadinha. Nem todos os objetos podem ser copiados dessa forma porque alguns campos de objeto podem ser privados e não serão visíveis fora do próprio objeto.

Há ainda mais um problema com a abordagem direta. Uma vez que você precisa saber a classe do objeto para criar uma cópia, seu código se torna dependente daquela classe. Se a dependência adicional não te assusta, tem ainda outra pegadinha. Algumas vezes você só sabe a interface que o objeto segue, mas não sua classe concreta, quando, por exemplo, um parâmetro em um método aceita quaisquer objetos que seguem uma interface.

Solução

O padrão Prototype delega o processo de clonagem para o próprio objeto que está sendo clonado. O padrão declara uma interface comum para todos os objetos que suportam clonagem. Essa interface permite que você clone um objeto sem acoplar seu código à classe daquele objeto. Geralmente, tal interface contém apenas um único método clonar.

A implementação do método clonar é muito parecida em todas as classes. O método cria um objeto da classe atual e carrega todos os valores de campo do antigo objeto para o novo. Você pode até mesmo copiar campos privados porque a maioria das linguagens de programação permite objetos acessar campos privados de outros objetos que pertençam à mesma classe.

Um objeto que suporta clonagem é chamado de um protótipo. Quando seus objetos têm dúzias de campos e centenas de possíveis configurações, cloná-los pode servir como uma alternativa às subclasses.

Estrutura

  1. Protótipo: Declara os métodos de clonagem. Na maioria dos casos é apenas um método clonar.
  2. Protótipos Concretos: Implementam o método de clonagem.
  3. Cliente: Pode produzir uma cópia de qualquer objeto que segue a interface do protótipo.

Exemplo em PHP

Interface Protótipo

interface Prototype {
    public function clonar();
}

Protótipos Concretos

class Circulo implements Prototype {
    public $raio;
    public $cor;
 
    public function __construct($raio, $cor) {
        $this->raio = $raio;
        $this->cor = $cor;
    }
 
    public function clonar() {
        return new Circulo($this->raio, $this->cor);
    }
}
 
class Retangulo implements Prototype {
    public $largura;
    public $altura;
    public $cor;
 
    public function __construct($largura, $altura, $cor) {
        $this->largura = $largura;
        $this->altura = $altura;
        $this->cor = $cor;
    }
 
    public function clonar() {
        return new Retangulo($this->largura, $this->altura, $this->cor);
    }
}

Código Cliente

$circulo = new Circulo(10, 'vermelho');
$circuloClone = $circulo->clonar();
 
$retangulo = new Retangulo(20, 30, 'azul');
$retanguloClone = $retangulo->clonar();
 
echo "Círculo original: Raio = {$circulo->raio}, Cor = {$circulo->cor}\n";
echo "Círculo clone: Raio = {$circuloClone->raio}, Cor = {$circuloClone->cor}\n";
 
echo "Retângulo original: Largura = {$retangulo->largura}, Altura = {$retangulo->altura}, Cor = {$retangulo->cor}\n";
echo "Retângulo clone: Largura = {$retanguloClone->largura}, Altura = {$retanguloClone->altura}, Cor = {$retanguloClone->cor}\n";

Exemplo em Node.js

Interface Protótipo

class Prototype {
    clonar() {
        throw new Error("Método 'clonar()' deve ser implementado.");
    }
}

Protótipos Concretos

class Circulo extends Prototype {
    constructor(raio, cor) {
        super();
        this.raio = raio;
        this.cor = cor;
    }
 
    clonar() {
        return new Circulo(this.raio, this.cor);
    }
}
 
class Retangulo extends Prototype {
    constructor(largura, altura, cor) {
        super();
        this.largura = largura;
        this.altura = altura;
        this.cor = cor;
    }
 
    clonar() {
        return new Retangulo(this.largura, this.altura, this.cor);
    }
}

Código Cliente

const circulo = new Circulo(10, 'vermelho');
const circuloClone = circulo.clonar();
 
const retangulo = new Retangulo(20, 30, 'azul');
const retanguloClone = retangulo.clonar();
 
console.log(`Círculo original: Raio = ${circulo.raio}, Cor = ${circulo.cor}`);
console.log(`Círculo clone: Raio = ${circuloClone.raio}, Cor = ${circuloClone.cor}`);
 
console.log(`Retângulo original: Largura = ${retangulo.largura}, Altura = ${retangulo.altura}, Cor = ${retangulo.cor}`);
console.log(`Retângulo clone: Largura = ${retanguloClone.largura}, Altura = ${retanguloClone.altura}, Cor = ${retanguloClone.cor}`);

Aplicabilidade

  • Utilize o padrão Prototype quando seu código não deve depender de classes concretas de objetos que você precisa copiar.
  • Utilize o padrão quando você precisa reduzir o número de subclasses que somente diferem na forma que inicializam seus respectivos objetos.

Prós e Contras

Prós

  • Você pode clonar objetos sem acoplá-los a suas classes concretas.
  • Você pode se livrar de códigos de inicialização repetidos em troca de clonar protótipos pré-construídos.
  • Você pode produzir objetos complexos mais convenientemente.
  • Você tem uma alternativa para herança quando lidar com configurações pré-determinadas para objetos complexos.

Contras

  • Clonar objetos complexos que têm referências circulares pode ser bem complicado.

Relações com outros padrões

  • Muitos projetos começam usando o Factory Method e evoluem para o Abstract Factory, Prototype, ou Builder.
  • Classes Abstract Factory são quase sempre baseadas em um conjunto de métodos fábrica, mas você também pode usar o Prototype para compor métodos dessas classes.
  • O Prototype pode ajudar quando você precisa salvar cópias de comandos no histórico.
  • Projetos que fazem um uso pesado de Composite e do Decorator podem se beneficiar com frequência do uso do Prototype.
  • O Prototype não é baseado em heranças, então ele não tem os inconvenientes dela. Por outro lado, o Prototype precisa de uma inicialização complicada do objeto clonado.
  • Algumas vezes o Prototype pode ser uma alternativa mais simples a um Memento.