Aturan untuk bekerja dengan array dinamis dan kelas koleksi khusus



Aturan untuk bekerja dengan array dinamis dan kelas koleksi khusus
Berikut adalah aturan yang saya patuhi ketika bekerja dengan array dinamis. Sebenarnya, ini adalah panduan untuk merancang array, tetapi saya tidak ingin memasukkannya ke dalam panduan untuk mendesain objek, karena tidak setiap bahasa berorientasi objek memiliki array dinamis. Contoh-contoh ditulis dalam PHP karena mirip dengan Java (yang mungkin sudah Anda kenal), tetapi dengan array dinamis alih-alih kelas koleksi dan antarmuka bawaan.

Menggunakan array sebagai daftar


Semua item harus dari jenis yang sama.


Jika Anda menggunakan array sebagai daftar (kumpulan nilai dalam urutan tertentu), maka semua nilai harus dari jenis yang sama:

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

Gaya penjelasan jenis daftar umum adalah: @var array<TypeOfElment> . Pastikan Anda tidak menambahkan tipe indeks, itu harus selalu int .

Kita perlu mengabaikan indeks setiap elemen


PHP akan secara otomatis membuat indeks baru untuk setiap item daftar (0, 1, 2, dll.). Namun, Anda tidak boleh mengandalkan indeks ini, atau menggunakannya secara langsung. Klien hanya dapat mengandalkan countable iterable dan countable .

Jadi, Anda dapat dengan bebas menggunakan foreach dan count() , tetapi jangan gunakan for menggilir item-item daftar:

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

Dalam PHP, for loop mungkin tidak berfungsi sama sekali jika tidak ada indeks dalam daftar, atau jika ada lebih banyak indeks daripada jumlah elemen.

Gunakan filter alih-alih menghapus item


Anda mungkin ingin menghapus item berdasarkan indeks ( unset() ), tetapi alih-alih menghapusnya, lebih baik membuat daftar baru tanpa elemen yang tidak diinginkan menggunakan array_filter() .

Sekali lagi, seseorang tidak harus bergantung pada indeks elemen. Jadi ketika menggunakan array_filter() jangan gunakan parameter flag untuk menyaring elemen berdasarkan indeks, atau bahkan oleh elemen dan indeks.

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

Menggunakan Array sebagai Array Asosiatif


Jika kunci relevan dan bukan indeks (0, 1, 2, dll.), Maka gunakan array asosiatif secara bebas (koleksi dari mana Anda dapat mengekstraksi nilai dengan kunci unik mereka).

Semua kunci harus dari jenis yang sama.


Aturan pertama menggunakan array asosiatif: semua kunci harus dari tipe yang sama (paling sering adalah string ).

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

Semua nilai harus dari tipe yang sama.


Hal yang sama berlaku untuk nilai: mereka harus dari tipe yang sama.

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

Gaya umum untuk @var array<TypeOfKy, TypeOfValue> keterangan jenis adalah: @var array<TypeOfKy, TypeOfValue> .

Array asosiatif harus tetap pribadi


Daftar, karena kesederhanaan karakteristiknya, dapat dengan aman dipindahkan dari satu objek ke objek lainnya. Setiap klien dapat menggilir elemen atau menghitungnya, bahkan jika daftar itu kosong. Peta lebih sulit untuk dikerjakan, karena klien dapat mengandalkan kunci yang tidak cocok dengan nilai apa pun. Ini berarti bahwa array asosiatif biasanya harus tetap pribadi sehubungan dengan objek yang mengelolanya. Alih-alih membiarkan klien mengakses pemetaan internal secara langsung, biarkan getter (dan mungkin setter) mengambil nilai. Lempar pengecualian jika tidak ada nilai untuk kunci yang diminta. Namun, jika Anda dapat menyimpan peta dan nilainya sepenuhnya pribadi, lakukanlah.

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

Gunakan objek sebagai array asosiatif dengan nilai dari beberapa tipe


Jika Anda ingin menggunakan array asosiatif, tetapi menyimpan nilai dari berbagai jenis di dalamnya, maka gunakan objek. Tentukan kelas, tambahkan properti tipe publik, atau tambahkan konstruktor dan getter. Objek tersebut termasuk objek konfigurasi atau perintah:

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

Pengecualian terhadap Aturan


Perpustakaan dan kerangka kerja kadang-kadang membutuhkan penggunaan array yang lebih dinamis. Maka tidak mungkin (dan tidak diinginkan) untuk mengikuti aturan sebelumnya. Contohnya termasuk array data yang disimpan dalam tabel database, dan konfigurasi formulir di Symfony.

Kelas Koleksi Kustom


Kelas koleksi kustom dapat menjadi alat yang hebat untuk bekerja dengan Iterator , ArrayAccess dan entitas lainnya, tetapi saya menemukan kode ini sering membingungkan. Siapa pun yang melihat kode untuk pertama kalinya harus berkonsultasi dengan manual PHP, bahkan jika ia adalah pengembang yang berpengalaman. Selain itu, Anda harus menulis lebih banyak kode untuk dipelihara (tes, debug, dll.). Jadi dalam kebanyakan kasus, array sederhana dengan anotasi jenis yang tepat sudah cukup.

Apa yang menunjukkan bahwa Anda perlu membungkus array dalam objek koleksi khusus?

  • Duplikasi logika yang terkait dengan array.
  • Klien harus bekerja dengan terlalu banyak detail tentang isi array.

Gunakan kelas koleksi khusus untuk mencegah logika duplikat.


Jika beberapa klien yang bekerja dengan array yang sama melakukan tugas yang sama (misalnya, memfilter, membandingkan, mengurangi, menghitung), maka duplikat dapat dihapus menggunakan kelas koleksi kustom. Mentransfer logika duplikat ke metode kelas koleksi memungkinkan setiap klien untuk melakukan tugas yang sama hanya dengan memanggil metode pengumpulan:

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

Keuntungan mengubah koleksi menggunakan metode adalah bahwa transformasi ini dinamai. Anda dapat menambahkan nama pendek dan informatif untuk memanggil array_filter() , yang jika tidak akan cukup sulit ditemukan.

Lepaskan ikatan pelanggan dengan kelas koleksi khusus


Jika klien siklus melalui array, mengambil bagian dari data dari elemen yang dipilih dan melakukan sesuatu dengan mereka, klien ini menjadi lekat dengan semua jenis yang sesuai: array, elemen, nilai yang diambil, metode pemilih, dll. bahwa karena ikatan yang begitu dalam, akan jauh lebih sulit bagi Anda untuk mengubah apa pun yang terkait dengan jenis ini tanpa melanggar klien. Dalam hal ini, Anda juga dapat membungkus array dalam kelas koleksi khusus dan memberikan jawaban yang benar, melakukan perhitungan yang diperlukan di dalam dan melonggarkan ikatan klien ke koleksi.

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

Beberapa aturan untuk kelas koleksi khusus


Buat mereka tidak berubah


Saat melakukan transformasi seperti itu, referensi yang ada ke instance koleksi tidak boleh terpengaruh. Oleh karena itu, metode apa pun yang melakukan konversi ini harus mengembalikan instance kelas baru, seperti yang kita lihat dalam contoh sebelumnya:

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

Tentu saja, jika Anda mengonversi array internal, maka Anda dapat mengonversi ke tipe koleksi lain atau array sederhana. Seperti biasa, pastikan jenis yang benar dikembalikan.

Hanya berikan perilaku yang benar-benar dibutuhkan pelanggan


Alih-alih memperluas kelas dari perpustakaan dengan koleksi universal, atau menerapkan filter atau peta universal, serta mengurangi untuk setiap kelas koleksi kustom, hanya menerapkan apa yang benar-benar Anda butuhkan. Jika pada suatu saat Anda berhenti menggunakan metode ini, maka hapus saja.

Gunakan IteratorAggregate dan ArrayIterator untuk beralih


Jika Anda bekerja dengan PHP, maka alih-alih menerapkan semua metode antarmuka Iterator (menyimpan pointer internal, dll.), ArrayIterator hanya antarmuka IteratorAggregate dan biarkan ia mengembalikan instance ArrayIterator berdasarkan array internal:

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

Kompromi


Karena Anda menulis lebih banyak kode untuk kelas koleksi khusus, akan lebih mudah bagi klien untuk bekerja dengan koleksi ini (dan bukan hanya dengan satu larik). Jika kode klien menjadi lebih jelas, jika koleksi memberikan perilaku yang bermanfaat, maka ini membenarkan upaya ekstra untuk mempertahankan kelas koleksi kustom. Tetapi karena bekerja dengan array dinamis sangat mudah (terutama karena Anda tidak perlu menentukan jenis yang digunakan), saya jarang menggunakan kelas koleksi saya. Namun, beberapa pengembang aktif menggunakannya, jadi saya pasti akan terus mencari kemungkinan penggunaan case.

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


All Articles