قواعد للعمل مع المصفوفات الديناميكية وفئات المجموعة المخصصة



قواعد للعمل مع المصفوفات الديناميكية وفئات المجموعة المخصصة
فيما يلي القواعد التي ألتزم بها عند العمل مع المصفوفات الديناميكية. في الواقع ، هذا دليل لتصميم الصفائف ، لكنني لم أرغب في وضعها في دليل لتصميم الكائنات ، لأن ليس كل لغة موجهة للكائن بها صفائف ديناميكية. الأمثلة مكتوبة بلغة PHP لأنها تشبه Java (والتي قد تكون على دراية بها بالفعل) ، ولكن مع المصفوفات الديناميكية بدلاً من فئات واجهات التجميع المدمجة.

استخدام المصفوفات كقائمة


يجب أن تكون جميع العناصر من نفس النوع.


إذا كنت تستخدم صفيفًا كقائمة (مجموعة من القيم بترتيب معين) ، فيجب أن تكون جميع القيم من نفس النوع:

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

نمط التعليقات التوضيحية لنوع القائمة الشائعة هو: @var array<TypeOfElment> . تأكد من عدم إضافة نوع فهرس ، يجب أن يكون دائمًا int .

من الضروري تجاهل فهرس كل عنصر


سيقوم PHP تلقائيًا بإنشاء فهرس جديد لكل عنصر قائمة (0 ، 1 ، 2 ، إلخ). ومع ذلك ، يجب ألا تعتمد على هذه الفهارس ، ولا تستخدمها مباشرةً. يمكن للعملاء الاعتماد فقط على countable iterable .

لذلك يمكنك استخدام foreach و count() ، ولكن لا تستخدم للتجول حول عناصر القائمة:

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

في PHP ، قد لا تعمل حلقة for على الإطلاق إذا لم تكن هناك فهارس في القائمة ، أو إذا كان هناك فهارس أكثر من عدد العناصر.

استخدم عامل تصفية بدلاً من حذف العناصر


قد ترغب في حذف العناصر حسب الفهرس ( unset() ) ، ولكن بدلاً من الحذف ، من الأفضل إنشاء قائمة جديدة بدون عناصر غير مرغوب فيها باستخدام array_filter() .

مرة أخرى ، لا ينبغي للمرء أن يعتمد على مؤشرات العناصر. لذلك عند استخدام array_filter() لا تستخدم المعلمة flag لتصفية العناصر حسب الفهرس ، أو حتى حسب العنصر والفهرس.

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

استخدام المصفوفات كصفائف تجميعية


إذا كانت المفاتيح ذات صلة وليست فهارس (0 ، 1 ، 2 ، وما إلى ذلك) ، فاستخدم الصفائف الترابطية بحرية (مجموعة يمكنك من خلالها استخراج القيم بواسطة مفاتيحها الفريدة).

يجب أن تكون جميع المفاتيح من نفس النوع.


القاعدة الأولى لاستخدام المصفوفات الترابطية: يجب أن تكون جميع المفاتيح من نفس النوع (غالبًا ما تكون string ).

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

يجب أن تكون جميع القيم من نفس النوع.


الأمر نفسه ينطبق على القيم: يجب أن تكون من نفس النوع.

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

النمط الشائع @var array<TypeOfKy, TypeOfValue> نوع ما هو: @var array<TypeOfKy, TypeOfValue> .

المصفوفات الترابطية يجب أن تظل خاصة


القوائم ، بسبب بساطة خصائصها ، يمكن نقلها بأمان من كائن إلى كائن. يمكن لأي عميل التنقل بين العناصر أو حسابها ، حتى لو كانت القائمة فارغة. الخريطة أكثر صعوبة في العمل ، لأن العملاء يمكنهم الاعتماد على مفاتيح لا تتطابق مع أي قيمة. هذا يعني أن المصفوفات الترابطية يجب أن تظل خاصة فيما يتعلق بالكائنات التي تديرها. بدلاً من السماح للعملاء بالوصول إلى mapps الداخلية مباشرةً ، اسمح لـ getters (وربما المستوطنين) باسترداد القيم. استثناءات رمي ​​إذا لم يكن هناك قيمة للمفتاح المطلوب. ومع ذلك ، إذا كان يمكنك الحفاظ على الخريطة وقيمها خاصة تمامًا ، فافعل ذلك.

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

استخدم الكائنات كصفائف ربط مع قيم من عدة أنواع


إذا كنت تريد استخدام صفيف اقتران ، ولكن قم بتخزين قيم الأنواع المختلفة فيه ، فاستخدم الكائنات. حدد فئة ، أو أضف خصائص النوع العام ، أو أضف مُنشئًا ورسائل. تتضمن هذه الكائنات كائنات التكوين أو الأمر:

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

استثناءات من القاعدة


تتطلب المكتبات والأطر في بعض الأحيان استخدام المصفوفات بشكل أكثر ديناميكية. ثم أنه من المستحيل (وغير مرغوب فيه) اتباع القواعد السابقة. تتضمن الأمثلة صفيف البيانات المخزنة في جدول قاعدة البيانات ، وتكوين النموذج في Symfony.

فئات مجموعة مخصصة


يمكن أن تكون فئات المجموعة المخصصة أداة رائعة للعمل مع Iterator و ArrayAccess وكيانات أخرى ، لكنني أجد أن الشفرة غالبًا ما تكون مربكة. يجب على أي شخص ينظر إلى الشفرة لأول مرة الرجوع إلى دليل PHP ، حتى لو كان مطورًا ذو خبرة. بالإضافة إلى ذلك ، سيتعين عليك كتابة المزيد من التعليمات البرمجية للمحافظة عليها (الاختبار ، التصحيح ، إلخ). لذلك في معظم الحالات ، يكون الصفيف البسيط الذي يحتوي على التعليقات التوضيحية الصحيحة صحيحًا.

ما يشير إلى أنك تحتاج إلى التفاف الصفيف في كائن مجموعة مخصصة؟

  • الازدواجية في المنطق المتعلقة بالصفيف.
  • يجب على العملاء التعامل مع الكثير من التفاصيل حول محتويات الصفيف.

استخدم فئة مجموعة مخصصة لمنع تكرار المنطق.


إذا كان العديد من العملاء الذين يعملون مع نفس الصفيف يقومون بنفس المهمة (على سبيل المثال ، التصفية والمقارنة والتقليل والعدد) ، فيمكن إزالة التكرارات باستخدام فئة المجموعة المخصصة. يسمح نقل المنطق المكرر إلى طريقة فئة المجموعة لأي عميل بأداء نفس المهمة ببساطة عن طريق استدعاء طريقة التجميع:

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

تتمثل ميزة تحويل مجموعة باستخدام طريقة في تسمية هذا التحول. يمكنك إضافة اسم قصير array_filter() للاتصال array_filter() ، والذي سيكون من الصعب العثور عليه.

إلغاء ربط العملاء بفئة مجموعة مخصصة


إذا كان العميل يتنقل عبر صفيف ، ويأخذ جزءًا من البيانات من العناصر المحددة ويفعل شيئًا معها ، يصبح هذا العميل مرتبطًا ارتباطًا وثيقًا بجميع الأنواع المطابقة: المصفوفة ، والعناصر ، والقيم المستردة ، وطريقة الانتقاء ، إلخ. وبسبب هذا الارتباط العميق ، سيكون من الصعب عليك تغيير أي شيء متعلق بهذه الأنواع دون كسر العميل. في هذه الحالة ، يمكنك أيضًا التفاف الصفيف في فئة مجموعة مخصصة وإعطاء الإجابة الصحيحة ، وإجراء العمليات الحسابية اللازمة داخل وتخفيف ارتباط العميل بالمجموعة.

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

بعض القواعد لفئات المجموعة المخصصة


اجعلها غير قابلة للتغيير


عند إجراء مثل هذه التحويلات ، يجب ألا تتأثر المراجع الموجودة إلى مثيل المجموعة. لذلك ، يجب أن تُرجع أي طريقة تنفذ هذا التحويل نسخة جديدة من الفصل ، كما رأينا في المثال السابق:

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

بالطبع ، إذا قمت بتحويل صفيف داخلي ، فيمكنك التحويل إلى نوع آخر من المجموعات أو صفيف بسيط. كالعادة ، تأكد من إرجاع النوع الصحيح.

فقط توفير السلوك يحتاج العملاء حقا


بدلاً من توسيع فئة من مكتبة مع مجموعة عالمية ، أو تنفيذ مرشح أو خريطة عالمية ، وكذلك تقليل لكل فئة مجموعة مخصصة ، قم بتنفيذ ما تحتاجه فقط. إذا توقفت عن استخدام الطريقة في مرحلة ما ، فاحذفها.

استخدم IteratorAggregate و ArrayIterator للتكرار


إذا كنت تعمل مع PHP ، فبدلاً من تطبيق جميع أساليب واجهة Iterator (حفظ المؤشرات الداخلية ، وما إلى ذلك) ، قم بتطبيق واجهة IteratorAggregate فقط ، ArrayIterator مثيل ArrayIterator استنادًا إلى الصفيف الداخلي:

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

تسوية


نظرًا لأنك تكتب المزيد من التعليمات البرمجية لفئة مجموعة مخصصة ، فيجب أن يكون من الأسهل على العملاء التعامل مع هذه المجموعة (وليس فقط مع صفيف). إذا أصبح رمز العميل أكثر وضوحًا ، إذا كانت المجموعة توفر سلوكًا مفيدًا ، فإن هذا يبرر الجهد الإضافي للحفاظ على فئة مجموعة مخصصة. ولكن نظرًا لأن العمل مع المصفوفات الديناميكية أمر سهل جدًا (نظرًا لأنك لست بحاجة إلى تحديد الأنواع المستخدمة) ، نادراً ما أستخدم فئات المجموعات الخاصة بي. ومع ذلك ، فإن بعض المطورين يستخدمونها بنشاط ، لذلك سأستمر بالتأكيد في البحث عن حالات الاستخدام الممكنة.

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


All Articles