Reglas para trabajar con matrices dinámicas y clases de colección personalizadas
Estas son las reglas a las que me adhiero cuando trabajo con matrices dinámicas. De hecho, esta es una guía para diseñar matrices, pero no quería ponerla en una guía para diseñar objetos, porque no todos los lenguajes orientados a objetos tienen matrices dinámicas. Los ejemplos están escritos en PHP porque es similar a Java (con el que ya puede estar familiarizado), pero con matrices dinámicas en lugar de las clases e interfaces de colección incorporadas.
Usar matrices como una lista
Todos los artículos deben ser del mismo tipo.
Si usa una matriz como una lista (una colección de valores en un cierto orden), entonces todos los valores deben ser del mismo tipo:
$goodList = [ 'a', 'b' ]; $badList = [ 'a', 1 ];
El estilo de anotación de tipo de lista común es:
@var array<TypeOfElment>
. Asegúrese de no agregar un tipo de índice, siempre debe ser
int
.
Es necesario ignorar el índice de cada elemento.
PHP creará automáticamente un nuevo índice para cada elemento de la lista (0, 1, 2, etc.). Sin embargo, no debe confiar en estos índices ni utilizarlos directamente. Los clientes solo pueden confiar en
countable
iterable
y
countable
.
Por lo tanto, puede usar libremente
foreach
y
count()
, pero no use
for
recorrer los elementos de la lista:
En PHP, el bucle
for
puede no funcionar en absoluto si no hay índices en la lista, o si hay más índices que el número de elementos.
Use un filtro en lugar de eliminar elementos
Es posible que desee eliminar elementos por índice (
unset()
), pero en lugar de eliminarlo, es mejor crear una nueva lista sin elementos no deseados utilizando
array_filter()
.
Nuevamente, uno no debe confiar en los índices de elementos. Entonces, cuando use
array_filter()
no use
el parámetro flag para filtrar elementos por índice, o incluso por elemento e índice.
Usar matrices como matrices asociativas
Si las claves son relevantes y no son índices (0, 1, 2, etc.), utilice libremente matrices asociativas (una colección de la que puede extraer valores mediante sus claves únicas).
Todas las claves deben ser del mismo tipo.
La primera regla del uso de matrices asociativas: todas las claves deben ser del mismo tipo (la mayoría de las veces es una
string
).
$goodMap = [ 'foo' => 'bar', 'bar' => 'baz' ];
Todos los valores deben ser del mismo tipo.
Lo mismo se aplica a los valores: deben ser del mismo tipo.
$goodMap = [ 'foo' => 'bar', 'bar' => 'baz' ];
Un estilo común para anotar un tipo es:
@var array<TypeOfKy, TypeOfValue>
.
Las matrices asociativas deben permanecer privadas
Las listas, debido a la simplicidad de sus características, se pueden transferir de forma segura de un objeto a otro. Cualquier cliente puede recorrer los elementos o contarlos, incluso si la lista está vacía. Es más difícil trabajar con el mapa, porque los clientes pueden confiar en claves que no coinciden con ningún valor. Esto significa que las matrices asociativas generalmente deben permanecer privadas con respecto a los objetos que las administran. En lugar de permitir que los clientes accedan directamente a mapas internos, permita que los captadores (y posiblemente los definidores) recuperen valores. Lanza excepciones si no hay valor para la clave solicitada. Sin embargo, si puede mantener el mapa y sus valores completamente privados, hágalo.
Utilice objetos como matrices asociativas con valores de varios tipos.
Si desea usar una matriz asociativa, pero almacenar valores de diferentes tipos en ella, use objetos. Defina una clase, agregue propiedades de tipo público o agregue un constructor y captadores. Dichos objetos incluyen objetos de configuración o comando:
final class SillyRegisterUserCommand { public string $username; public string $plainTextPassword; public bool $wantsToReceiveSpam; public int $answerToIAmNotARobotQuestion; }
Excepciones a la regla
Las bibliotecas y los marcos a veces requieren el uso de matrices de forma más dinámica. Entonces es imposible (e indeseable) seguir las reglas anteriores. Los ejemplos incluyen la
matriz de datos que se almacena en una tabla de base de datos y
la configuración del formulario en Symfony.
Clases de colección personalizadas
Las clases de colección personalizadas pueden ser una gran herramienta para trabajar con
ArrayAccess
y otras entidades, pero el código a menudo se vuelve confuso. Cualquiera que busque código por primera vez tendrá que consultar el manual de PHP, incluso si es un desarrollador experimentado. Además, tendrá que escribir más código para mantener (prueba, depuración, etc.). Entonces, en la mayoría de los casos, una simple matriz con las anotaciones de tipo correcta es suficiente.
¿Qué indica que necesita envolver la matriz en un objeto de colección personalizado?
- Duplicación de lógica relacionada con una matriz.
- Los clientes tienen que trabajar con demasiados detalles sobre el contenido de la matriz.
Use una clase de colección personalizada para evitar la lógica duplicada.
Si varios clientes que trabajan con la misma matriz realizan la misma tarea (por ejemplo, filtrar, comparar, reducir, contar), se pueden eliminar los duplicados utilizando la clase de colección personalizada. La transferencia de lógica duplicada a un método de clase de colección permite a cualquier cliente realizar la misma tarea simplemente llamando al método de colección:
$names = [];
La ventaja de transformar una colección usando un método es que se nombra esta transformación. Puede agregar un nombre corto e informativo para llamar a
array_filter()
, que de lo contrario sería bastante difícil de encontrar.
Desvincula a los clientes con una clase de colección personalizada
Si un cliente realiza un ciclo a través de una matriz, toma parte de los datos de los elementos seleccionados y hace algo con ellos, este cliente se une estrechamente a todos los tipos correspondientes: matriz, elementos, valores recuperados, método selector, etc. que debido a un enlace tan profundo, será mucho más difícil para usted cambiar cualquier cosa relacionada con estos tipos sin romper el cliente. En este caso, también puede envolver la matriz en una clase de colección personalizada y dar la respuesta correcta, realizar los cálculos necesarios dentro y aflojar el enlace del cliente a la colección.
$lines = []; $sum = 0; foreach ($lines as $line) { if ($line->isComment()) { continue; } $sum += $line->quantity(); }
Algunas reglas para clases de colección personalizadas
Hazlos inmutables
Al realizar tales transformaciones, las referencias existentes a la instancia de colección no deberían verse afectadas. Por lo tanto, cualquier método que realice esta conversión debería devolver una nueva instancia de la clase, como vimos en el ejemplo 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( ); } }
Por supuesto, si convierte una matriz interna, puede convertirla a otro tipo de colección o una matriz simple. Como de costumbre, asegúrese de que se devuelva el tipo correcto.
Solo brinde el comportamiento que los clientes realmente necesitan
En lugar de expandir una clase desde una biblioteca con una colección universal, o implementar un filtro o mapa universal, así como reducir para cada clase de colección personalizada, implemente solo lo que realmente necesita. Si en algún momento deja de usar el método, bórrelo.
Use IteratorAggregate y ArrayIterator para iterar
Si está trabajando con PHP, en lugar de implementar todos los métodos de la interfaz
Iterator
(guardar punteros internos, etc.), implemente solo la interfaz
ArrayIterator
y deje que devuelva una instancia de
ArrayIterator
basada en la 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) {
Compromiso
Como está escribiendo más código para una clase de colección personalizada, debería ser más fácil para los clientes trabajar con esta colección (y no solo con una matriz). Si el código del cliente se vuelve más claro, si la colección proporciona un comportamiento útil, esto justifica el esfuerzo adicional de mantener una clase de colección personalizada. Pero dado que trabajar con matrices dinámicas es muy fácil (principalmente porque no necesita especificar los tipos utilizados), rara vez uso mis clases de colección. Sin embargo, algunos desarrolladores los usan activamente, por lo que definitivamente continuaré buscando posibles casos de uso.