O Padrão Composite é um padrão estrutural que nos permite criar objetos em estruturas individuais ou compostas similares a uma árvore. Onde cada objeto, tanto individual (leaf) como composto(composite), serão tratados da mesma forma. Em outras palavras, você consegue criar componentes que ignoram as diferenças entre objetos individuais ou compostos.
Quando me refiro a componentes compostos, é que o padrão Composite faz a ligação hierárquica ligando vários objetos a um único através da recursividade. Ou seja, ele permite que você componha objetos em uma estrutura similar a um árvore para representar hierarquias todo-parte.
Exemplo de uso do Padrão de Projeto Composite
Quando modelamos um problema fica mais fácil compreende-lo. Imagine que você vai construir um componente de um catálogo de produtos. Você precisará de dois objetos, um Produto que apresenta as folhas(leaf) da árvore e um catálogo (Composite) para criar a coleção de folhas. Utilizando o diagrama anterior, a representação dessa ideia seria essa:
Vamos modelar esta ideia. Primeiro vamos criar o contrato que define o Catalog Component. Só que para implementar o padrão Composite, temos um problema para resolver em nossas classes. Observe que tanto o objeto simples (leaf) quanto o composto (composite), estendem as características de Component, só que na leaf, não deve ter os comportamentos de add, remove e getChild. Para contornar isso, você pode definir os métodos em Component com um chamada explícita de exceção. Com isso, você força seus filhos a não utilizarem os métodos e forçar o Composite a subscrever estes métodos por métodos concretos.
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Structural\Composite; use BadMethodCallException; abstract class CatalogComponent { protected function add(CatalogComponent $catalogComponent): void { throw new BadMethodCallException('Method not implemented'); } protected function remove(CatalogComponent $catalogComponent): void { throw new BadMethodCallException('Method not implemented'); } protected function getChild(CatalogComponent $catalogComponent): CatalogComponent { throw new BadMethodCallException('Method not implemented'); } abstract protected function display(): void; }
Segundo o diagrama de classe do Composite, os componentes filhos precisam implementar um método comum, no nosso exemplo é o método é o display assinado como abstrato para forçar sua implementação;
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Structural\Composite; final class Product extends CatalogComponent { private string $name; private float $price; public function __construct(string $name, float $price) { $this->name = $name; $this->price = $price; } public function display(): void { printf("Product: %s R$ %.2f\n", $this->name, $this->price); } }
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Structural\Composite; use SplObjectStorage; final class ProductCatalog extends CatalogComponent { private SplObjectStorage $products; public function __construct(private string $name) { $this->products = new SplObjectStorage(); } public function add(CatalogComponent $catalogComponent): void { $this->products->attach($catalogComponent); } public function remove(CatalogComponent $catalogComponent): void { $this->products->detach($catalogComponent); } public function getChild(CatalogComponent $catalogComponent): CatalogComponent { if (!$this->products->contains($catalogComponent)) { throw new \InvalidArgumentException('CatalogComponent not found'); } return $catalogComponent; } public function display(): void { echo "Catalog: {$this->name}\n"; foreach ($this->products as $product) { $product->display(); } } }
Observe que o catálogo de produto por ser um componente, consegue adicionar hierarquicamente produtos para o compor:
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Tests\Structural\Composite; use Growthdev\DesignPatterns\Structural\Composite\Product; use Growthdev\DesignPatterns\Structural\Composite\ProductCatalog; use PHPUnit\Framework\TestCase; final class CompositeTest extends TestCase { public function testCanCreateProductCatalog(): void { $catalog = new ProductCatalog('Catalog 1'); $catalog2 = new ProductCatalog('Catalog 2'); $catalog->add(new Product('Product 1', 10.0)); $catalog->add(new Product('Product 2', 20.0)); $catalog->add($catalog2); $catalog->getChild($catalog2)->add(new Product('Product 3', 30.0)); $catalog->getChild($catalog2)->add(new Product('Product 4', 40.0)); $catalog->getChild($catalog2)->add(new Product('Product 5', 50.0)); $catalog->display(); $this->expectOutputString( "Catalog: Catalog 1\n" . "Product: Product 1 R$ 10.00\n" . "Product: Product 2 R$ 20.00\n" . "Catalog: Catalog 2\n" . "Product: Product 3 R$ 30.00\n" . "Product: Product 4 R$ 40.00\n" . "Product: Product 5 R$ 50.00\n" ); } }
Este exemplo ilustra muito bem o uso deste padrão de projetos . Não estava previsto para este artigo, mas para deixá-lo um pouco mais didático, vou modelar mais um problema para resolvermos com o uso do Composite Pattern.
Modelando mais um resultado com o Padrão de Projeto Composite
Certamente em algum momento você teve que manipular HTML. Se você observar, ele carrega todos os princípios do padrão Composite. Ele tem tags, como <img/> ou <h1></h1> que representam a leaf e outras tags que que conseguem adicionar outras em uma estrutura hierarquizada como uma árvore, como exemplo a tag <div> que pode receber diversas outras tags, etc…
A nossa classe para representar nosso componente principal será TagComponent. Seguido da classe Tag que representa leaf e da classe TagComposite.
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Structural\Composite\Html; abstract class TagComponent { public function add(TagComponent $tagComponent): void { throw new \BadMethodCallException('Method not implemented'); } public function getChild(TagComponent $tagComponent): TagComponent { throw new \BadMethodCallException('Method not implemented'); } abstract public function display(): void; }
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Structural\Composite\Html; class Tag extends TagComponent { private string $name; public function __construct(string $name, private ?string $content = null) { $this->name = $name; } public function display(): void { if ($this->content) { echo "<{$this->name}>{$this->content}</{$this->name}>\n"; } else { echo "<{$this->name} />\n"; } } }
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Structural\Composite\Html; use SplObjectStorage; class TagComposite extends TagComponent { private SplObjectStorage $components; public function __construct(private string $name) { $this->components = new SplObjectStorage(); } public function add(TagComponent $tagComponent): void { $this->components->attach($tagComponent); } public function getChild(TagComponent $tagComponent): TagComponent { if (!$this->components->contains($tagComponent)) { throw new \InvalidArgumentException('TagComponent not found'); } return $tagComponent; } public function display(): void { echo "<{$this->name}>\n"; foreach ($this->components as $component) { $component->display(); } echo "</{$this->name}>\n"; } }
Veja como fica mais fácil visualizar o uso deste padrão quando conseguimos fazer uma comparação com algo que já conhecemos que tem comportamento semelhante.
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Tests\Structural\Composite\Html; use Growthdev\DesignPatterns\Structural\Composite\Html\Tag; use Growthdev\DesignPatterns\Structural\Composite\Html\TagComposite; use PHPUnit\Framework\TestCase; final class TagCompositeTest extends TestCase { public function testCanCreateHtmlComposite(): void { $html = new TagComposite('html'); $head = new TagComposite('head'); $head->add(new Tag('title', 'Hello World')); $html->add($head); $body = new TagComposite('body'); $div = new TagComposite('div'); $body->add($div); $body->getChild($div)->add(new Tag('img')); $body->getChild($div)->add(new Tag('h1', 'Growth Dev')); $html->add($body); $html->display(); $this->expectOutputString( "<html>\n" . "<head>\n" . "<title>Hello World</title>\n" . "</head>\n" . "<body>\n" . "<div>\n" . "<img />\n" . "<h1>Growth Dev</h1>\n" . "</div>\n" . "</body>\n" . "</html>\n" ); } }
Chegamos ao fim de mais um artigo, se você gostou, deixe seu comentário e compartilhe com o máximo de pessoas que você puder. Isso vai me ajudar enormemente!
Os códigos fontes de todos os exemplos estão disponíveis em meu Github:
Resumo dos Padrões de Projetos (Design Patterns)
Se você quiser se aprofundar um pouco mais sobre Padrões de Projetos, não deixe de acesso a página
Até o próximo artigo!
Confiança Sempre!!!
Fontes:
Seja o primeiro a comentar