DEV Community

Cover image for Padrão de projeto Prototype em PHP
Fabio Hiroki for Textos em Português

Posted on

Padrão de projeto Prototype em PHP

Vamos imaginar que você está trabalhando em um eCommerce e recebeu a responsabilidade de implementar a seguinte funcionalidade:

Na tela do carrinho, o usuário deve ser capaz de adicionar mais de uma unidade de um produto já presente, e também alterar a sua cor.

Uma solução intuitiva nesse contexto poderia ser instanciar um novo objeto produto e setar todos os atributos iguais ao produto original. Mas isso parece ser muito verboso, e você precisaria saber todo o funcionamento interno da classe produto, o que parece quebrar o encapsulamento.

Prototype ao resgate!

Gif de alguém sendo clonado

Do refactoring.guru:

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

Isso significa que você não precisa usar o operador new nem se preocupar em como configurar uma instância copiada perfeitamente. Só precisa o usar o operador clone e o PHP vai resolver tudo sozinho.

Aplicação de exemplo

Versão final do código está no Github:

GitHub logo fabiothiroki / php-design-patterns

A collection of design patterns written in PHP

A classe Cart não faz parte do padrão de projeto, mas está presente aqui somente para demonstrar como o padrão se encaixaria numa aplicação real:

final class Cart
{
    /**
     * @var ProductPrototype[]
     */
    private array $products;

    public function addProduct(ProductPrototype $product): void
    {
        $this->products[] = $product;
    }

    /**
     * @return ProductPrototype[]
     */
    public function getProducts(): array
    {
        return $this->products;
    }
}
Enter fullscreen mode Exit fullscreen mode

Como podemos observar, somos capazes de adicionar um novo produto e verificar quais produtos já estão adicionados.

abstract class ProductPrototype
{
    protected int $id;
    protected string $name;
    protected string $color;

    public function getId(): int
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getColor(): string
    {
        return $this->color;
    }

    public function setColor(string $color): void
    {
        $this->color = $color;
    }
}
Enter fullscreen mode Exit fullscreen mode

PHP já implementa Prototype nativamente porque todo objeto já aceita o operador clone por padrão. Nesse exemplo, criei a classe ProductPrototype só para ilustrar um caso real: para a classe Cart não importa qual o tipo específico de produto será adicionado, contanto que tenha os atributos id, name e color.

Apenas para simplificar esse exemplo, vou mostrar dois tipos de produto: Smartphone e Laptop:

final class Smartphone extends ProductPrototype
{
    public function __construct()
    {
        $this->id = 1;
        $this->name = 'Smartphone';
        $this->color = 'Default color';
    }
}

final class Laptop extends ProductPrototype
{
    public function __construct()
    {
        $this->id = 2;
        $this->name = 'Smartphone';
        $this->color = 'Default color';
    }
}
Enter fullscreen mode Exit fullscreen mode

Explicando Prototype com testes unitários

Gif de uma casa se multiplicando

O que o operador clone realmente faz? Ele copia uma instância com os mesmos atributos. Então no nosso exemplo, o produto clonado terá os mesmos valores de id, nome e cor do objeto original.

public function testSmartphoneClone(): void
{
    $smartphone = new Smartphone();

    $clonedSmartphone = clone $smartphone;

    self::assertEquals($clonedSmartphone->getId(), $smartphone->getId());
    self::assertEquals($clonedSmartphone->getName(), $smartphone->getName());
    self::assertEquals($clonedSmartphone->getColor(), $smartphone->getColor());
    self::assertEquals($clonedSmartphone, $smartphone);
    self::assertNotSame($clonedSmartphone, $smartphone);
}
Enter fullscreen mode Exit fullscreen mode

Como podemos observar, se tivéssemos que clonar um objeto manualmente, teríamos que saber todas as propriedades a serem copiadas e como setá-las.

Perceba que esse teste não precisaria ser escrito numa aplicação real porque podemos assumir que o operador clone funciona corretamente, já que é provido pelo próprio PHP.

De volta ao nosso caso de uso

Finalmente podemos dar ao Cart a funcionalidade de aumentar a quantidade de um produto existente e alterar sua cor. Cart ficará ainda mais feliz porque não precisa se preocupar se estará recebendo uma instância de Laptop ou de Smartphone.

É só fazer uso do operador clone e adicionar o produto recém clonado:

public function testCartCanAddClonedProducts(): void
{
    $laptop = new Laptop();
    $cart = new Cart();

    $cart->addProduct($laptop);

    $clonedLaptop = clone $laptop;
    $clonedLaptop->setColor('White');
    $cart->addProduct($clonedLaptop);

    self::assertCount(2, $cart->getProducts());
    self::assertEquals($cart->getProducts()[1]->getId(), $cart->getProducts()[0]->getId());
    self::assertEquals($cart->getProducts()[1]->getName(), $cart->getProducts()[0]->getName());
    self::assertEquals('White', $cart->getProducts()[1]->getColor());
    self::assertEquals('Default color', $cart->getProducts()[0]->getColor());
}
Enter fullscreen mode Exit fullscreen mode

Gif do bob esponja se multiplicando

Obrigado por ler até aqui, espero que você tenha entendido melhor sobre esse padrão de projeto e lembre-se de usá-lo quando necessário!

Discussion (0)