Regras para trabalhar com matrizes dinâmicas e classes de coleção personalizadas



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:

 // Good loop: foreach ($list as $element) { } // Bad loop (exposes the index of each element): foreach ($list as $index => $element) { } // Also bad loop (the index of each element should not be used): for ($i = 0; $i < count($list); $i++) { } 

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.

 // Good filter: array_filter( $list, function (string $element): bool { return strlen($element) > 2; } ); // Bad filter (uses the index to filter elements as well) array_filter( $list, function (int $index): bool { return $index > 3; }, ARRAY_FILTER_USE_KEY ); // Bad filter (uses both the index and the element to filter elements) array_filter( $list, function (string $element, int $index): bool { return $index > 3 || $element === 'Include'; }, ARRAY_FILTER_USE_BOTH ); 

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' ]; // Bad (uses different types of keys) $badMap = [ 'foo' => 'bar', 1 => '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' ]; // Bad (uses different types of values) $badMap = [ 'foo' => 'bar', 'bar' => 1 ]; 

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.

 // Exposing a list is fine /** * @return array<User> */ public function allUsers(): array { // ... } // Exposing a map may be troublesome /** * @return array<string, User> */ public function usersById(): array { // ... } // Instead, offer a method to retrieve a value by its key /** * @throws UserNotFound */ public function userById(string $id): User { // ... } 

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 = [/* ... */]; // Found in several places: $shortNames = array_filter( $names, function (string $element): bool { return strlen($element) < 5; } ); // Turned into a custom collection class: use Assert\Assert; final class Names { /** * @var array<string> */ private array $names; public function __construct(array $names) { Assert::that()->allIsString($names); $this->names = $names; } public function shortNames(): self { return new self( array_filter( $this->names, function (string $element): bool { return strlen($element) < 5; } ) ); } } $names = new Names([/* ... */]); $shortNames = $names->shortNames(); 

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(); } // Turned into a custom collection class: final class Lines { public function totalQuantity(): int { $sum = 0; foreach ($lines as $line) { if ($line->isComment()) { continue; } $sum += $line->quantity(); } return $sum; } } 

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 { /** * @var array<string> */ 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 { /** * @var array<string> */ 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.

Source: https://habr.com/ru/post/pt484336/


All Articles