Regeln für die Arbeit mit dynamischen Arrays und benutzerdefinierten Auflistungsklassen



Regeln für die Arbeit mit dynamischen Arrays und benutzerdefinierten Auflistungsklassen
Hier sind die Regeln, die ich beim Arbeiten mit dynamischen Arrays befolge. Tatsächlich ist dies eine Anleitung zum Entwerfen von Arrays, aber ich wollte sie nicht in eine Anleitung zum Entwerfen von Objekten aufnehmen, da nicht jede objektorientierte Sprache dynamische Arrays hat. Die Beispiele sind in PHP geschrieben, da sie Java ähneln (mit dem Sie möglicherweise bereits vertraut sind), aber mit dynamischen Arrays anstelle der integrierten Auflistungsklassen und -schnittstellen.

Verwenden von Arrays als Liste


Alle Artikel müssen vom selben Typ sein.


Wenn Sie ein Array als Liste verwenden (eine Sammlung von Werten in einer bestimmten Reihenfolge), müssen alle Werte vom gleichen Typ sein:

$goodList = [ 'a', 'b' ]; $badList = [ 'a', 1 ]; 

Der gebräuchliche Annotationsstil für @var array<TypeOfElment> lautet: @var array<TypeOfElment> . Stellen Sie sicher, dass Sie keinen Indextyp hinzufügen, er sollte immer int .

Der Index jedes Elements muss ignoriert werden


PHP erstellt automatisch einen neuen Index für jedes Listenelement (0, 1, 2 usw.). Sie sollten sich jedoch weder auf diese Indizes verlassen noch sie direkt verwenden. Kunden können sich nur auf iterable und countable .

So können Sie foreach und count() frei verwenden, aber nicht, um Listenelemente zu durchlaufen:

 // 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++) { } 

In PHP funktioniert die for Schleife möglicherweise überhaupt nicht, wenn die Liste keine Indizes enthält oder wenn mehr Indizes als die Anzahl der Elemente vorhanden sind.

Verwenden Sie einen Filter, anstatt Elemente zu löschen


Möglicherweise möchten Sie Elemente nach Index löschen ( unset() ), aber anstatt zu löschen, ist es besser, mit array_filter() eine neue Liste ohne unerwünschte Elemente zu erstellen.

Auch hier sollte man sich nicht auf Elementindizes verlassen. Verwenden array_filter() bei Verwendung von array_filter() nicht den Parameter flag , um Elemente nach Index oder sogar nach Element und Index herauszufiltern.

 // 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 ); 

Verwenden von Arrays als assoziative Arrays


Wenn die Schlüssel relevant sind und keine Indizes (0, 1, 2 usw.), verwenden Sie frei assoziative Arrays (eine Auflistung, aus der Sie Werte anhand ihrer eindeutigen Schlüssel extrahieren können).

Alle Schlüssel müssen vom selben Typ sein.


Die erste Regel für die Verwendung von assoziativen Arrays: Alle Schlüssel müssen vom gleichen Typ sein (meistens handelt es sich um string ).

 $goodMap = [ 'foo' => 'bar', 'bar' => 'baz' ]; // Bad (uses different types of keys) $badMap = [ 'foo' => 'bar', 1 => 'baz' ]; 

Alle Werte müssen vom selben Typ sein.


Gleiches gilt für Werte: Sie müssen vom gleichen Typ sein.

 $goodMap = [ 'foo' => 'bar', 'bar' => 'baz' ]; // Bad (uses different types of values) $badMap = [ 'foo' => 'bar', 'bar' => 1 ]; 

Ein gebräuchlicher Stil zum Kommentieren eines Typs ist: @var array<TypeOfKy, TypeOfValue> .

Assoziative Arrays müssen privat bleiben


Listen können aufgrund der Einfachheit ihrer Eigenschaften sicher von Objekt zu Objekt übertragen werden. Jeder Client kann Elemente durchlaufen oder zählen, auch wenn die Liste leer ist. Es ist schwieriger, mit Map zu arbeiten, da Clients sich auf Schlüssel verlassen können, die keinem Wert entsprechen. Dies bedeutet, dass assoziative Arrays in Bezug auf die Objekte, die sie verwalten, normalerweise privat bleiben sollten. Anstatt Clients den direkten Zugriff auf interne Zuordnungen zu ermöglichen, können Getter (und möglicherweise Setter) Werte abrufen. Ausnahmen auslösen, wenn für den angeforderten Schlüssel kein Wert vorhanden ist. Wenn Sie jedoch die Karte und ihre Werte vollständig geheim halten können, tun Sie dies.

 // 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 { // ... } 

Verwenden Sie Objekte als assoziative Arrays mit Werten verschiedener Typen


Wenn Sie ein assoziatives Array verwenden möchten, aber Werte unterschiedlichen Typs darin speichern möchten, verwenden Sie Objekte. Definieren Sie eine Klasse, fügen Sie öffentliche Typeneigenschaften hinzu oder fügen Sie einen Konstruktor und Getter hinzu. Solche Objekte umfassen Konfigurations- oder Befehlsobjekte:

 final class SillyRegisterUserCommand { public string $username; public string $plainTextPassword; public bool $wantsToReceiveSpam; public int $answerToIAmNotARobotQuestion; } 

Ausnahmen von der Regel


Bibliotheken und Frameworks erfordern manchmal eine dynamischere Verwendung von Arrays. Dann ist es unmöglich (und unerwünscht), die vorherigen Regeln zu befolgen. Beispiele sind das Array von Daten , die in einer Datenbanktabelle gespeichert sind, und die Formularkonfiguration in Symfony.

Benutzerdefinierte Sammlungsklassen


Benutzerdefinierte Auflistungsklassen können ein großartiges Werkzeug für die Arbeit mit Iterator , ArrayAccess und anderen Entitäten sein, aber ich finde, dass der Code oft verwirrend wird. Wer sich zum ersten Mal mit Code befasst, muss das PHP-Handbuch konsultieren, auch wenn er ein erfahrener Entwickler ist. Außerdem müssen Sie mehr Code schreiben, um zu warten (testen, debuggen usw.). In den meisten Fällen ist ein einfaches Array mit den richtigen Typanmerkungen ausreichend.

Was bedeutet, dass Sie das Array in ein benutzerdefiniertes Auflistungsobjekt einschließen müssen?

  • Vervielfältigung der Logik in Bezug auf ein Array.
  • Clients müssen mit zu vielen Details über den Inhalt des Arrays arbeiten.

Verwenden Sie eine benutzerdefinierte Auflistungsklasse, um doppelte Logik zu verhindern.


Wenn mehrere Clients, die mit demselben Array arbeiten, dieselbe Aufgabe ausführen (z. B. Filtern, Vergleichen, Reduzieren, Zählen), können Duplikate mithilfe der benutzerdefinierten Auflistungsklasse entfernt werden. Durch die Übertragung doppelter Logik in eine Sammlungsklassenmethode kann jeder Client dieselbe Aufgabe ausführen, indem er einfach die Sammlungsmethode aufruft:

 $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(); 

Der Vorteil der Transformation einer Auflistung mithilfe einer Methode besteht darin, dass diese Transformation benannt wird. Sie können einen kurzen und informativen Namen für den Aufruf von array_filter() , der sonst ziemlich schwer zu finden wäre.

Binden Sie Kunden mit einer benutzerdefinierten Auflistungsklasse auf


Wenn ein Client ein Array durchläuft, einen Teil der Daten aus den ausgewählten Elementen entnimmt und etwas damit unternimmt, wird dieser Client eng mit allen entsprechenden Typen verknüpft: Array, Elemente, abgerufene Werte, Auswahlmethode usw. dass es aufgrund einer solch tiefen Bindung für Sie viel schwieriger sein wird, irgendetwas in Bezug auf diese Typen zu ändern, ohne den Client zu beschädigen. In diesem Fall können Sie das Array auch in eine benutzerdefinierte Auflistungsklasse einschließen und die richtige Antwort geben, die erforderlichen Berechnungen im Array ausführen und die Bindung des Clients an die Auflistung lösen.

 $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; } } 

Einige Regeln für benutzerdefinierte Auflistungsklassen


Mach sie unveränderlich


Bei der Durchführung solcher Transformationen sollten die vorhandenen Verweise auf die Auflistungsinstanz nicht betroffen sein. Daher sollte jede Methode, die diese Konvertierung ausführt, eine neue Instanz der Klasse zurückgeben, wie wir im vorherigen Beispiel gesehen haben:

 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( /* ... */ ); } } 

Wenn Sie ein internes Array konvertieren, können Sie natürlich in einen anderen Auflistungstyp oder ein einfaches Array konvertieren. Stellen Sie wie gewohnt sicher, dass der richtige Typ zurückgegeben wird.

Stellen Sie nur das Verhalten zur Verfügung, das Kunden wirklich benötigen


Anstatt eine Klasse aus einer Bibliothek mit einer universellen Sammlung zu erweitern oder einen universellen Filter oder eine universelle Zuordnung zu implementieren und für jede benutzerdefinierte Sammlungsklasse zu reduzieren, implementieren Sie nur das, was Sie wirklich benötigen. Wenn Sie die Methode irgendwann nicht mehr verwenden, löschen Sie sie.

Verwenden Sie IteratorAggregate und ArrayIterator zum Iterieren


Wenn Sie mit PHP arbeiten, implementieren Sie anstelle aller Methoden der Iterator Schnittstelle (Speichern interner Zeiger usw.) nur die IteratorAggregate Schnittstelle und lassen Sie sie eine ArrayIterator Instanz basierend auf dem internen Array zurückgeben:

 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) { // ... } 

Kompromiss


Da Sie mehr Code für eine benutzerdefinierte Auflistungsklasse schreiben, sollte es für Clients einfacher sein, mit dieser Auflistung (und nicht nur mit einem Array) zu arbeiten. Wenn der Clientcode klarer wird und die Auflistung nützliches Verhalten bietet, ist der zusätzliche Aufwand für die Verwaltung einer benutzerdefinierten Auflistungsklasse gerechtfertigt. Da die Arbeit mit dynamischen Arrays jedoch so einfach ist (hauptsächlich, weil Sie die verwendeten Typen nicht angeben müssen), verwende ich meine Auflistungsklassen selten. Einige Entwickler setzen sie jedoch aktiv ein, sodass ich auf jeden Fall weiter nach möglichen Anwendungsfällen suchen werde.

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


All Articles