Ir ao conteúdo

Padrão de Projeto Iterator em PHP com exemplo

Com o uso do Padrão de Projeto Iterator, conseguimos acessar e percorrer elementos em uma coleção de dados sem expor sua implementação. Ou seja, não importa se é uma lista, pilha ou uma árvore, etc…Conseguimos acessar sequencialmente os elementos de um objeto agregado sem expor sua representação subjacente. Refinando um pouco mais esse conceito, o padrão Iterator literalmente assume a responsabilidade de acessar os elementos sequencialmente da coleção e transfere essa responsabilidade  para o objeto Iterator.

Recentemente fiz o artigo Pilha: Estrutura de Dados em PHP Orientado a Objetos e utilizei parte do Padrão de Projeto Iterator. Mas, neste artigo vou mostrar outro exemplo para você fixar o entendimento de uso do Iterator Patten.

O Padrão Iterator  define prioritariamente duas interfaces, uma para representar a coleção de dados, o Aggregator, e outra para definir as regras de acesso aos elementos da coleção, o Iterator.

Diagrama Padrão de Projeto Iterator
Diagrama Padrão de Projeto Iterator

É muito fácil encontrar um exemplo para utilizar o Padrão Iterator, só pensar em alguma coleção de dados qualquer e implementar o recurso para percorrer os seus elementos. Por exemplo, vamos criar um agregador de tarefas.

Vamos criar primeiro o Task Value Object para representar a tarefa.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Iterator;

final class Task
{
    private string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

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

Muitos não sabem, mas o PHP possui uma biblioteca completa para manipular estruturas de dados, a Standard PHP Library (SPL). E lá conseguimos utilizar diversos recursos, inclusive Iteradores.  Mas não vamos utilizar estes Iteradores da SPL neste artigo.

Além disso, o PHP trás algumas interfaces e classes pré-definidas, onde temos disponível uma interface chamada Iterator, que podemos utilizar direto no nosso código. Vamos criar o nosso Iterador de tarefas e ver como fica no código:

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Iterator;

use Iterator;
use ArrayObject;

final class TaskIterator implements Iterator
{
    private ArrayObject $tasks;
    private int $position = 0;

    public function __construct(ArrayObject $tasks)
    {
        $this->tasks = $tasks;
    }

    public function current(): Task
    {
        return $this->tasks[$this->position];
    }

    public function key(): int
    {
        return $this->position;
    }

    public function next(): void
    {
        ++$this->position;
    }

    public function rewind(): void
    {
        $this->position = 0;
    }

    public function valid(): bool
    {
        return isset($this->tasks[$this->position]);
    }
}

Veja que utilizei o ArrayObject, que pertence a SPL do PHP como alternativa ao uso da função array() para representar nossa coleção de objetos.

Da mesma forma que o PHP trás a interface Iterator, ele nos fornece também a interface IteratorAggregate, que podemos utilizar nos nossos projetos. Vamos implementar nosso TaskList com esta interface.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Iterator;

use ArrayObject;
use Iterator;
use IteratorAggregate;

final class TaskList implements IteratorAggregate
{
    private ArrayObject $tasks;

    public function __construct(ArrayObject $tasks)
    {
        $this->tasks = $tasks;
    }

    public function getIterator(): Iterator
    {
        return new TaskIterator($this->tasks);
    }
}

Veja como o nosso código fica organizado na utilização do Padrão de Projeto Iterator

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Tests\Behavioral\Iterator;

use ArrayObject;
use Growthdev\DesignPatterns\Behavioral\Iterator\Task;
use Growthdev\DesignPatterns\Behavioral\Iterator\TaskList;
use PHPUnit\Framework\TestCase;

final class TaskIteratorTest extends TestCase
{
    public function testCanIterateOverTaskList(): void
    {
        $tasks = new ArrayObject();
        $tasks->append(new Task('Task 1'));
        $tasks->append(new Task('Task 2'));
        $tasks->append(new Task('Task 3'));
        $tasks->append(new Task('Task 4'));

        $taskList = new TaskList($tasks);
        $iterator = $taskList->getIterator();

        $id = 1;
        while ($iterator->valid()) {
            $currentTask = $iterator->current();
            $this->assertEquals(sprintf('Task %d', $id), $currentTask->getName());
            $iterator->next();
            $id++;
        }
    }
}
Resultado dos testes Iterator Pattern
Resultado dos testes Iterator Pattern

O PHP tem suas peculiaridades, mas os recursos que utilizamos aqui, nos permite deixar nossos códigos melhores e mais fáceis de manter.

Porque utilizei o ArrayObject ao invés da função array?

Para esse caso, utilizei o ArrayObject porque de fato eu queria criar uma coleção de Objetos do Tipo Task. Além disso, o nosso código ficou semanticamente mais interessante. Porém, será que em questão de desempenho, utilizar Array Object ao invés de array nativo, será que consumiria mais recurso? Vamos fazer um benchmarking “superficial” para analisarmos:

  • Processador: Intel(R) Core(TM) i7-1165G7 @ 2.80GHz   1.69 GHz
  • RAM: 16 GB
  • PHP: 8.0.12

Rodando uma quantidade pequena de dados, como exemplo 1 mil registros, temos o seguinte resultado:

<?php

require_once __DIR__ . '/../../../vendor/autoload.php';

use Growthdev\DesignPatterns\Behavioral\Iterator\Task;

$begin = microtime(true);
$taskArray = array();

for ($i=0; $i < 1000; $i++) {
    $taskArray[] = new Task("Task $i");
};

$end = microtime(true);
$execution_time = $end - $begin;

printf("\nMemory using array: %2.f MB\n", memory_get_usage()/(1024 * 1024));
printf("Execution time using array: %2.f seconds\n", $execution_time);
Resultado do  benchmarking  de Array Nativo vs Array Object
Resultado do benchmarking de Array Nativo vs Array Object

<?php

require_once __DIR__ . '/../../../vendor/autoload.php';

use Growthdev\DesignPatterns\Behavioral\Iterator\Task;

$begin = microtime(true);
$taskObject = new ArrayObject;

for ($i=0; $i < 1000; $i++) {
    $taskObject->append(new Task("Task $i"));
};

$end = microtime(true);
$execution_time = $end - $begin;

printf("\nMemory using ArrayObject: %2.f MB\n", memory_get_usage()/(1024 * 1024));
printf("Execution time using ArrayObject: %2.f seconds\n", $execution_time);
Resultado do  benchmarking  de Array Nativo vs Array Object segundo resultado
Resultado do benchmarking de Array Nativo vs Array Object segundo resultado

Quase imperceptível, não é verdade? Agora vamos rodar com 1 milhão de registros:

Resultado do  benchmarking  de Array Nativo vs Array Object nova tentativa
Resultado do benchmarking de Array Nativo vs Array Object nova tentativa

Resultado do benchmarking de Array Nativo vs Array Object segunda nova tentativa
Resultado do benchmarking de Array Nativo vs Array Object segunda nova tentativa

Todos os códigos deste artigos, estão disponíveis no meu Github

https://github.com/growthdev-repo/design-patterns

Você pode encontrar mais artigos sobre Padrões de Projetos no artigo Resumo dos Padrões de Projetos (Design Patterns) e também no Descomplicando os Padrões de Projetos (Design Patterns)

Chegamos ao fim de mais um artigo feito com muita dedicação e empenho. Se você gostou deste artigo, deixe o seu comentário e compartilhe com os seus amigos para que eles possam evoluir em suas carreiras e com isso você ajuda meu site a crescer e alcançar mais pessoas. Nos vemos no próximo artigo!

Confiança Sempre!!!

Publicado emDesign PatternPadrões de ProjetosPHP

Seja o primeiro a comentar

    Deixe um comentário

    O seu endereço de e-mail não será publicado.