Regras para trabalhar com matrizes dinâmicas e classes de coleção personalizadas
Aqui estão as regras às quais eu aderi ao trabalhar com matrizes dinâmicas. Na verdade, este é um guia para projetar matrizes, mas eu não queria colocá-lo em um guia para projetar objetos, porque nem toda linguagem orientada a objetos possui matrizes dinâmicas. Os exemplos são escritos em PHP porque é semelhante ao Java (com o qual você já deve estar familiarizado), mas com matrizes dinâmicas em vez das classes e interfaces de coleção internas.
Usando matrizes como uma lista
Todos os itens devem ser do mesmo tipo.
Se você usar uma matriz como uma lista (uma coleção de valores em uma determinada ordem), todos os valores deverão ser do mesmo tipo:
$goodList = [ 'a', 'b' ]; $badList = [ 'a', 1 ];
O estilo comum de anotação de tipo de lista é:
@var array<TypeOfElment>
. Certifique-se de não adicionar um tipo de índice, ele deve sempre ser
int
.
É necessário ignorar o índice de cada elemento
O PHP criará automaticamente um novo índice para cada item da lista (0, 1, 2, etc.). No entanto, você não deve confiar nesses índices nem usá-los diretamente. Os clientes podem confiar apenas em
countable
iterable
e
countable
.
Portanto, você pode usar livremente
foreach
e
count()
, mas não use
for
alternar entre itens da lista:
No PHP, o loop
for
pode não funcionar se não houver índices na lista ou se houver mais índices do que o número de elementos.
Use um filtro em vez de excluir itens
Você pode remover itens por índice (
unset()
), mas, em vez de excluir, é melhor criar uma nova lista sem elementos indesejados usando
array_filter()
.
Novamente, não se deve confiar em índices de elementos. Portanto, ao usar
array_filter()
não use
o parâmetro flag para filtrar elementos por índice, ou mesmo por elemento e índice.
Usando matrizes como matrizes associativas
Se as chaves forem relevantes e não índices (0, 1, 2, etc.), use livremente matrizes associativas (uma coleção da qual você pode extrair valores por suas chaves exclusivas).
Todas as chaves devem ser do mesmo tipo.
A primeira regra do uso de matrizes associativas: todas as chaves devem ser do mesmo tipo (na maioria das vezes é
string
).
$goodMap = [ 'foo' => 'bar', 'bar' => 'baz' ];
Todos os valores devem ser do mesmo tipo.
O mesmo se aplica aos valores: eles devem ser do mesmo tipo.
$goodMap = [ 'foo' => 'bar', 'bar' => 'baz' ];
Um estilo comum para anotar um tipo é:
@var array<TypeOfKy, TypeOfValue>
.
Matrizes associativas devem permanecer privadas
As listas, devido à simplicidade de suas características, podem ser transferidas com segurança de um objeto para outro. Qualquer cliente pode percorrer os elementos ou contá-los, mesmo que a lista esteja vazia. É mais difícil trabalhar com o mapa, porque os clientes podem confiar em chaves que não correspondem a nenhum valor. Isso significa que matrizes associativas geralmente devem permanecer privadas em relação aos objetos que os gerenciam. Em vez de permitir que os clientes acessem diretamente os mapas internos, permita que os getters (e possivelmente os setters) recuperem valores. Lance exceções se não houver valor para a chave solicitada. No entanto, se você puder manter o mapa e seus valores completamente privados, faça-o.
Use objetos como matrizes associativas com valores de vários tipos
Se você deseja usar uma matriz associativa, mas armazenar valores de tipos diferentes nela, use objetos. Defina uma classe, adicione propriedades de tipo público ou adicione um construtor e getters. Esses objetos incluem objetos de configuração ou comando:
final class SillyRegisterUserCommand { public string $username; public string $plainTextPassword; public bool $wantsToReceiveSpam; public int $answerToIAmNotARobotQuestion; }
Exceções à regra
Bibliotecas e estruturas às vezes exigem o uso de matrizes de forma mais dinâmica. Então é impossível (e indesejável) seguir as regras anteriores. Os exemplos incluem a
matriz de dados armazenados em uma tabela de banco de dados e
a configuração do formulário no Symfony.
Classes de coleção personalizada
As classes de coleção personalizadas podem ser uma ótima ferramenta para trabalhar com
Iterator
,
ArrayAccess
e outras entidades, mas acho que o código geralmente fica confuso. Qualquer pessoa que olhar código pela primeira vez terá que consultar o manual do PHP, mesmo que seja um desenvolvedor experiente. Além disso, você precisará escrever mais código para manter (teste, depuração etc.). Portanto, na maioria dos casos, basta uma matriz simples com as anotações de tipo corretas.
O que indica que você precisa agrupar a matriz em um objeto de coleção personalizado?
- Duplicação de lógica relacionada a uma matriz.
- Os clientes precisam trabalhar com muitos detalhes sobre o conteúdo da matriz.
Use uma classe de coleção personalizada para evitar lógica duplicada.
Se vários clientes que trabalham com a mesma matriz executam a mesma tarefa (por exemplo, filtrar, comparar, reduzir, contar), você poderá remover duplicatas usando a classe de coleção personalizada. Transferir lógica duplicada para um método de classe de coleção permite que qualquer cliente execute a mesma tarefa simplesmente chamando o método de coleção:
$names = [];
A vantagem de transformar uma coleção usando um método é que essa transformação é nomeada. Você pode adicionar um nome curto e informativo para chamar
array_filter()
, que de outra forma seria muito difícil de encontrar.
Desvincular clientes com uma classe de coleção personalizada
Se um cliente percorre uma matriz, toma parte dos dados dos elementos selecionados e faz algo com eles, esse cliente fica intimamente ligado a todos os tipos correspondentes: matriz, elementos, valores recuperados, método seletor, etc. que, devido a uma ligação tão profunda, será muito mais difícil alterar qualquer coisa relacionada a esses tipos sem prejudicar o cliente. Nesse caso, você também pode agrupar a matriz em uma classe de coleção personalizada e fornecer a resposta correta, realizando os cálculos necessários e afrouxando a ligação do cliente à coleção.
$lines = []; $sum = 0; foreach ($lines as $line) { if ($line->isComment()) { continue; } $sum += $line->quantity(); }
Algumas regras para classes de coleção personalizadas
Torná-los imutáveis
Ao executar essas transformações, as referências existentes à instância da coleção não devem ser afetadas. Portanto, qualquer método que execute essa conversão deve retornar uma nova instância da classe, como vimos no exemplo anterior:
final class Names { private array $names; public function __construct(array $names) { Assert::that()->allIsString($names); $this->names = $names; } public function shortNames(): self { return new self( ); } }
Obviamente, se você converter uma matriz interna, poderá converter para outro tipo de coleção ou uma matriz simples. Como sempre, verifique se o tipo correto foi retornado.
Forneça apenas o comportamento que os clientes realmente precisam
Em vez de expandir uma classe de uma biblioteca com uma coleção universal ou implementar um filtro ou mapa universal, além de reduzir para cada classe de coleção personalizada, implemente apenas o que você realmente precisa. Se em algum momento você parar de usar o método, exclua-o.
Use IteratorAggregate e ArrayIterator para iterar
Se você trabalha com PHP, em vez de implementar todos os métodos da interface
Iterator
(salvando ponteiros internos, etc.), implemente apenas a interface
IteratorAggregate
e deixe retornar uma instância de
ArrayIterator
base na matriz interna:
final class Names implements IteratorAggregate { private array $names; public function __construct(array $names) { Assert::that()->allIsString($names); $this->names = $names; } public function getIterator(): Iterator { return new ArrayIterator($this->names); } } $names = new Names([]); foreach ($names as $name) {
Compromisso
Como você está escrevendo mais código para uma classe de coleção personalizada, deve ser mais fácil para os clientes trabalhar com essa coleção (e não apenas com uma matriz). Se o código do cliente ficar mais claro, se a coleção fornecer um comportamento útil, isso justifica o esforço extra de manter uma classe de coleção personalizada. Mas como trabalhar com matrizes dinâmicas é muito fácil (principalmente porque você não precisa especificar os tipos usados), raramente uso minhas classes de coleção. No entanto, alguns desenvolvedores os estão usando ativamente, por isso continuarei a procurar possíveis casos de uso.