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
- Protótipo: Declara os métodos de clonagem. Na maioria dos casos é apenas um método
clonar. - Protótipos Concretos: Implementam o método de clonagem.
- 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.