الفرق بين المراجعتين ل"Design Patterns/iterator"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(2.1 محتوى)
(2.2 محتوى)
سطر 1: سطر 1:
 
<noinclude>{{DISPLAYTITLE:نمط المكرِّر Iterator}}</noinclude>
 
<noinclude>{{DISPLAYTITLE:نمط المكرِّر Iterator}}</noinclude>
 
نمط المكرِّر هو نمط تصميم سلوكي يسمح لك بتخطي عناصر من مجموعة (Collection) دون كشف التمثيل التحتي (underlying representation) لها (قائمة، مكدَّس، شجرة، إلخ).
 
نمط المكرِّر هو نمط تصميم سلوكي يسمح لك بتخطي عناصر من مجموعة (Collection) دون كشف التمثيل التحتي (underlying representation) لها (قائمة، مكدَّس، شجرة، إلخ).
 
+
==المشكلة==
== المشكلة ==
 
 
المجموعات هي إحدى أكثر أنواع البيانات استخدامًا في البرمجة، لكن رغم ذلك فهي لا تعدو كونها مجرد حاوية لمجموعة من الكائنات.
 
المجموعات هي إحدى أكثر أنواع البيانات استخدامًا في البرمجة، لكن رغم ذلك فهي لا تعدو كونها مجرد حاوية لمجموعة من الكائنات.
  
سطر 16: سطر 15:
  
 
ومن الناحية الأخرى فإن شيفرة العميل المفترض أن تعمل مع مجموعات متعددة قد لا تهتم أصلًا بالكيفية التي تخزن بها بياناتها، لكن بما أن المجموعات توفر طرقًا مختلفة من الوصول إلى عناصرها، فقد لا يكون لديك خيار سوى ربط شيفرتها بفئات مجموعات محددة.
 
ومن الناحية الأخرى فإن شيفرة العميل المفترض أن تعمل مع مجموعات متعددة قد لا تهتم أصلًا بالكيفية التي تخزن بها بياناتها، لكن بما أن المجموعات توفر طرقًا مختلفة من الوصول إلى عناصرها، فقد لا يكون لديك خيار سوى ربط شيفرتها بفئات مجموعات محددة.
 
+
==الحل==
== الحل ==
 
 
الوظيفة الأساسية لنمط المكرر هي استخراج سلوك التجاوز للمجموعة إلى كائن منفصل يسمى المكرِّر (Iterator).
 
الوظيفة الأساسية لنمط المكرر هي استخراج سلوك التجاوز للمجموعة إلى كائن منفصل يسمى المكرِّر (Iterator).
  
سطر 27: سطر 25:
  
 
ويجب أن تستخدم كل المكررات نفس الواجهة، هذا يجعل شيفرة العميل متوافقة مع أي نوع مجموعة أو أي خوارزمية تجاوز طالما أن هناك مكررًا مناسبًا، وإن احتجت أسلوبًا خاصًا لتجاوز مجموعة فأنشئ فئة مكرر جديد ببساطة، دون الحاجة إلى تغيير المجموعة أو العميل.
 
ويجب أن تستخدم كل المكررات نفس الواجهة، هذا يجعل شيفرة العميل متوافقة مع أي نوع مجموعة أو أي خوارزمية تجاوز طالما أن هناك مكررًا مناسبًا، وإن احتجت أسلوبًا خاصًا لتجاوز مجموعة فأنشئ فئة مكرر جديد ببساطة، دون الحاجة إلى تغيير المجموعة أو العميل.
 
+
==مثال من الواقع==
== مثال من الواقع ==
 
 
ضع الصورة. عدة طرق للسير في روما.
 
ضع الصورة. عدة طرق للسير في روما.
  
سطر 36: سطر 33:
  
 
تتصرف كل تلك الخيارات السابقة، سواء الإرشادات العشوائية أو تطبيق الهاتف أو المرشد المحلي، تتصرف جميعها كمكرِّرات على مجموعة المشاهد ومواقع الجذب السياحي في روما.
 
تتصرف كل تلك الخيارات السابقة، سواء الإرشادات العشوائية أو تطبيق الهاتف أو المرشد المحلي، تتصرف جميعها كمكرِّرات على مجموعة المشاهد ومواقع الجذب السياحي في روما.
 
+
==البنية==
== البنية ==
+
ضع الصورة.
ضع الصورة.  
+
#تصرح واجهة المكرر (Iterator) عن العمليات المطلوبة لتخطي مجموعة ما: جلب العنصر التالي، أو استرجاع الخيار الحالي، أو إعادة تشغيل التكرار، إلخ.
# تصرح واجهة المكرر (Iterator) عن العمليات المطلوبة لتخطي مجموعة ما: جلب العنصر التالي، أو استرجاع الخيار الحالي، أو إعادة تشغيل التكرار، إلخ.
+
#تستخدم المكرِّرات الحقيقية (Concrete Iterators) خوارزميات محددة لتخطي مجموعة ما، وينبغي أن يتتبع كائن المكرر تقدُّم عملية التخطي بنفسه، ذلك يسمح لعدة مكررات أن تتخطى نفس المجموعة بشكل مستقل عن بعضها البعض.
# تستخدم المكرِّرات الحقيقية (Concrete Iterators) خوارزميات محددة لتخطي مجموعة ما، وينبغي أن يتتبع كائن المكرر تقدُّم عملية التخطي بنفسه، ذلك يسمح لعدة مكررات أن تتخطى نفس المجموعة بشكل مستقل عن بعضها البعض.
+
#تصرح واجهة المجموعة (Collection) عن أسلوب واحد أو أكثر لجعل المكرِّرات تتوافق مع المجموعة، لاحظ أن نوع العودة (return type) للأساليب يجب أن يصرَّح عنه على أنه واجهة المكرِّر (iterator interface) كي تتمكن المجموعات الحقيقية من إعادة أنواع المكرِّرات المختلفة.
# تصرح واجهة المجموعة (Collection) عن أسلوب واحد أو أكثر لجعل المكرِّرات تتوافق مع المجموعة، لاحظ أن نوع العودة (return type) للأساليب يجب أن يصرَّح عنه على أنه واجهة المكرِّر (iterator interface) كي تتمكن المجموعات الحقيقية من إعادة أنواع المكرِّرات المختلفة.
+
#تعيد المجموعات الحقيقية (Concrete Collections) نسخًا جديدة من فئة مكرر حقيقي بعينه في كل مرة يطلب العميل واحدًا، وينبغي أن تكون بقية شيفرة المجموعة في نفس الفئة، لكننا نهملها لأن تلك التفاصيل ليست ضرورية للنمط الفعلي.
# تعيد المجموعات الحقيقية (Concrete Collections) نسخًا جديدة من فئة مكرر حقيقي بعينه في كل مرة يطلب العميل واحدًا، وينبغي أن تكون بقية شيفرة المجموعة في نفس الفئة، لكننا نهملها لأن تلك التفاصيل ليست ضرورية للنمط الفعلي.
+
#يعمل العميل (Client) مع المجموعات والمكرِّرات من خلال واجهاتها، وهكذا لا يُربط العميل بالفئات الحقيقية مما يسمح لك باستخدام مجموعات ومكررات متنوعة بنفس شيفرة العميل. ولا تنشئ العملاء عادة مكرِّرات بأنفسها، وإنما تحصل عليها من المجموعات، لكن يستطيع العميل إنشاء مكرر مباشرة في بعض الحالات، كأن يعرِّف العميل المكرِّر الخاص به.
# يعمل العميل (Client) مع المجموعات والمكرِّرات من خلال واجهاتها، وهكذا لا يُربط العميل بالفئات الحقيقية مما يسمح لك باستخدام مجموعات ومكررات متنوعة بنفس شيفرة العميل. ولا تنشئ العملاء عادة مكرِّرات بأنفسها، وإنما تحصل عليها من المجموعات، لكن يستطيع العميل إنشاء مكرر مباشرة في بعض الحالات، كأن يعرِّف العميل المكرِّر الخاص به.
+
==مثال وهمي==
 
 
== مثال وهمي ==
 
 
في هذا المثال يُستخدم نمط المكرِّر للمرور على نوع خاص من المجموعات تغلف الدخول إلى مخطط اجتماعي لفيس بوك، وتوفر المجموعة عدة مكرِّرات يمكنها تجاوز الحسابات بطرق مختلفة.
 
في هذا المثال يُستخدم نمط المكرِّر للمرور على نوع خاص من المجموعات تغلف الدخول إلى مخطط اجتماعي لفيس بوك، وتوفر المجموعة عدة مكرِّرات يمكنها تجاوز الحسابات بطرق مختلفة.
  
سطر 53: سطر 48:
  
 
لا تكون شيفرة العميل مرتبطة بالفئات الحقيقية لأنها تعمل مع المجموعات والمكرِّرات من خلال الواجهات فقط، فإن قررت ربط تطبيقك بشبكة اجتماعية جديدة، فلا تحتاج إلا إلى إضافة فئة مجموعة جديدة وفئة مكرِّر دون تغيير الشيفرة الحالية.<syntaxhighlight lang="java">
 
لا تكون شيفرة العميل مرتبطة بالفئات الحقيقية لأنها تعمل مع المجموعات والمكرِّرات من خلال الواجهات فقط، فإن قررت ربط تطبيقك بشبكة اجتماعية جديدة، فلا تحتاج إلا إلى إضافة فئة مجموعة جديدة وفئة مكرِّر دون تغيير الشيفرة الحالية.<syntaxhighlight lang="java">
// The collection interface must declare a factory method for
+
// يجب أن تصرِّح واجهة المجموعة عن أسلوب مصنع لإنتاج المكرِّرات.
// producing iterators. You can declare several methods if there
+
// تستطيع التصريح عن عدة أساليب إن كان لديك أنواعًا مختلفة من التكرار متاحةً في برنامجك.
 
// are different kinds of iteration available in your program.
 
// are different kinds of iteration available in your program.
 
interface SocialNetwork is
 
interface SocialNetwork is
سطر 61: سطر 56:
  
  
// Each concrete collection is coupled to a set of concrete
+
// تُربَط كل مجموعة حقيقية بمجموعة من فئات المكرِّر الحقيقية التي تعيدها.
// iterator classes it returns. But the client isn't, since the
+
// هذه الأساليب يعيد واجهات المكرِّر. (signature) لكن لا ينطبق ذلك على العميل لأن توقيع
// signature of these methods returns iterator interfaces.
 
 
class Facebook implements SocialNetwork is
 
class Facebook implements SocialNetwork is
     // ... The bulk of the collection's code should go here ...
+
     // ... توضع شيفرة المجموعة هنا ...
  
     // Iterator creation code.
+
     // شيفرة إنشاء المكرِّر.
 
     method createFriendsIterator(profileId) is
 
     method createFriendsIterator(profileId) is
 
         return new FacebookIterator(this, profileId, "friends")
 
         return new FacebookIterator(this, profileId, "friends")
سطر 74: سطر 68:
  
  
// The common interface for all iterators.
+
// الواجهة المشتركة لكل المكرِّرات.
 
interface ProfileIterator is
 
interface ProfileIterator is
 
     method getNext():Profile
 
     method getNext():Profile
سطر 80: سطر 74:
  
  
// The concrete iterator class.
+
// فئة المكرِّر الحقيقي.
 
class FacebookIterator implements ProfileIterator is
 
class FacebookIterator implements ProfileIterator is
     // The iterator needs a reference to the collection that it
+
     // يحتاج المكرِّر إلى مرجع إلى المجموعة التي يتخطاها
    // traverses.
 
 
     private field facebook: Facebook
 
     private field facebook: Facebook
 
     private field profileId, type: string
 
     private field profileId, type: string
  
     // An iterator object traverses the collection independently
+
     // يتخطى مكرِّر ما المجموعةَ بشكل مستقل عن باقي المكرِّرات.
    // from other iterators. Therefore it has to store the
+
     // لذا يجب أن يخزن حالة التكرار.
     // iteration state.
 
 
     private field currentPosition
 
     private field currentPosition
 
     private field cache: array of Profile
 
     private field cache: array of Profile
سطر 102: سطر 94:
 
             cache = facebook.socialGraphRequest(profileId, type)
 
             cache = facebook.socialGraphRequest(profileId, type)
  
     // Each concrete iterator class has its own implementation
+
     // الخاص بها من واجهة المكرِّر المشتركة (implementation) كل فئة مكرر حقيقي لها تطبيقها.
    // of the common iterator interface.
 
 
     method getNext() is
 
     method getNext() is
 
         if (hasMore())
 
         if (hasMore())
سطر 114: سطر 105:
  
  
// Here is another useful trick: you can pass an iterator to a
+
// تستطيع تمرير مكرِّرٍ إلى فئة عميل بدلًا من إعطائه وصولًا إلى المجموعة كلها.
// client class instead of giving it access to a whole
+
// وهكذا تتجنب كشف المجموعة للعميل.
// collection. This way, you don't expose the collection to the
 
// client.
 
 
//
 
//
// And there's another benefit: you can change the way the
+
// إحدى الفوائد الأخرى كذلك هي أنك تستطيع تغيير الطريقة التي يعمل بها العميل مع المجموعة
// client works with the collection at runtime by passing it a
+
// أثناء وقت التشغيل من خلال تمرير مكرر مختلف إليه.
// different iterator. This is possible because the client code
+
//وهذا ممكن التنفيذ بسبب أن شيفرة العميل ليست مرتبطة بفئات مكرر حقيقية.
// isn't coupled to concrete iterator classes.
 
 
class SocialSpammer is
 
class SocialSpammer is
 
     method send(iterator: ProfileIterator, message: string) is
 
     method send(iterator: ProfileIterator, message: string) is
سطر 130: سطر 118:
  
  
// The application class configures collections and iterators
+
// تهيئ فئة التطبيق المجموعات والمكرِّرات ثم تمررهم إلى شيفرة العميل.
// and then passes them to the client code.
 
 
class Application is
 
class Application is
 
     field network: SocialNetwork
 
     field network: SocialNetwork
سطر 151: سطر 138:
 
         spammer.send(iterator, "Very important message")
 
         spammer.send(iterator, "Very important message")
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
== قابلية التطبيق ==
 +
* '''استخدم نمط المكرِّر عندما تكون مجموعتك بها هيكل بيانات معقد وتريد إخفاء ذلك التعقيد عن العملاء (من أجل تسهيل تجربة الاستخدام أو لأسباب أمنية).'''
 +
يختزل المكرر تفاصيل العمل مع هيكل البيانات المعقد موفرًا عدة أساليب بسيطة للعميل للوصول إلى عناصر المجموعة. يسهل هذا المنظور تجربة الاستخدام للعميل كما يحمي المجموعة من الإهمال أو الأفعال الضارة التي قد ينفذها العميل إن كان بتعامل مباشرة مع المجموعة.
 +
* '''استخدم النمط لتقليل تكرار شيفرات التخطي في برنامجك.'''
 +
تميل شيفرة خوارزميات التكرار غير العادي إلى أن تكون كبيرة الحجم، وقد تشوش على مسؤولية الشيفرة الأصلية لبرنامج إن وُضعت داخل منطق العمل الخاص به، وكذلك قد تجعل تعديلها وإصلاحها أصعب. لهذا فإن نقل شيفرة التخطي (traverse code) إلى مكرِّرات محددة يجعل شيفرة البرنامج أكثر رشاقة ونظامًا.
 +
* '''استخدم المكرِّر حين تريد لشيفرتك أن تكون قادرة على تخطي هياكل بيانات مختلفة أو عند عدم معرفة أنواع من تلك الهياكل مسبقًا.'''
 +
يوفر النمط بضعة واجهات عامة لكل من المجموعات والمكرِّرات، وبما أن شيفرتك تستخدم الآن هذه الواجهات، فستعمل إن مررت إليها أنواعًا مختلفة من المجموعات والمكرِّرات التي تستخدم تلك الواجهات.
 +
 +
== كيفية الاستخدام ==
 +
# صرِّح عن واجهة المكرِّر، يجب أن يكون بها على أسلوبًا واحدًا على الأقل لجلب العنصر التالي من مجموعة، لكن يمكنك إضافة بعض الأساليب الأخرى من أجل التيسير على نفسك، مثل جلب العنصر السابق وتتبع الموضع الحالي وتفقد نهاية التكرار.
 +
# صرِّح عن واجهة المجموعة وصِفْ أسلوبًا لجلب المكرِّرات، يجب أن يكون نوع الإعادة (return type) مكافئًا لنوع إعادة واجهة المكرِّر. قد تصرِّح عن أساليب مشابهة إن كنت تخطط أن يكون لديك عدة مجموعات مميزة (distinct) من المكرِّرات.
 +
# استخدم فئات المكرر الحقيقي (concrete iterator) للمجموعات التي تريدها أن تكون قابلة للتخطي (traversable) بواسطة المكرِّرات، ويجب أن يكون كائن المكرر مرتبطًا بمجموعة واحدة فقط. عادة ما يُنشأ هذا الرابط من خلال منشئ المكرِّر (iterator's constructor).
 +
# استخدم واجهة المجموعة في فئات مجموعتك، غرض هذه الاستخدام هو تزويد العميل باختصار لإنشاء المكرِّرات المصممة من أجل فئة مجموعة بعينها، يجب أن يمرر كائن المجموعة نفسه إلى منشئ المكرِّر من أجل إنشاء رابط بينهما.
 +
# اذهب إلى شيفرة العميل لتغيير شيفرة تخطي المجموعة لإحلال المكرِّرات مكانها، سيجلب العميل كائن مكرِّر جديد في كل مرة يحتاج أن يمر فيها على عناصر المجموعة.
 +
 +
== المزايا والعيوب ==
 +
 +
=== المزايا ===
 +
مبدأ المسؤولية الواحدة. تستطيع تنفية شيفرة العميل والمجموعات باستخراج خوارزميات التخطي كبيرة الحجم إلى فئات منفصلة.
 +
 +
مبدأ المفتوح/المغلق. تستطيع استخدام أنواع جديدة من المجموعات والمكرِّرات وتمريرها إلى الشيفرة الحالية دون تعطيل أي شيء.
 +
 +
تستطيع المرور على نفس المجموعة بشكل متوازي، ذلك أن كل كائن مكرِّر يحتوي على حالة التكرار الخاصة به.
 +
 +
وأيضًا، لنفس السبب السابق، يمكنك تأخير تكرار واستكماله عند الحاجة.
 +
 +
=== العيوب ===
 +
قد يكون من الإسراف استخدام النمط إن كان برنامجك لا يعمل إلا مع المجموعات البسيطة.
 +
 +
قد يكون استخدام النمط أقل كفاءة من المرور على عناصر بعض المجموعات المخصصة مباشرة.
 +
 +
== العلاقات مع الأنماط الأخرى ==
 +
تستطيع استخدام المكرِّرات لتجاوز أشجار نمط [[Design Patterns/composite|المركَّب]].
 +
 +
تستطيع استخدام أسلوب المصنع مع المكرر من أجل السماح للفئات الفرعية لمجموعة ان تعيد أنواعًا مختلفة من المكررات المتوافقة معها.
 +
 +
تستطيع استخدام نمط التذكرة مع نمط المكرر لتسجيل حالة التكرار الحالية وإرجاعها لحالة سابقة عند الحاجة.
 +
 +
تستطيع استخدام نمط الزائر (Visitor) مع المكرِّر لتجاوز هيكل بيانات معقد وتنفيذ عملية ما على عناصره، حتى لو كانت عناصره كلها تحتوي على فئات مختلفة.
 +
 +
== الاستخدام في لغة جافا ==
 +
 +
== الاستخدام في لغة #C ==
 +
 +
== الاستخدام في لغة PHP ==
 +
 +
== الاستخدام في لغة بايثون ==
 +
 +
== الاستخدام في لغة روبي ==
 +
 +
== الاستخدام في لغة Swift ==
 +
 +
== الاستخدام في لغة TypeScript ==

مراجعة 05:05، 19 يوليو 2019

نمط المكرِّر هو نمط تصميم سلوكي يسمح لك بتخطي عناصر من مجموعة (Collection) دون كشف التمثيل التحتي (underlying representation) لها (قائمة، مكدَّس، شجرة، إلخ).

المشكلة

المجموعات هي إحدى أكثر أنواع البيانات استخدامًا في البرمجة، لكن رغم ذلك فهي لا تعدو كونها مجرد حاوية لمجموعة من الكائنات.

ضع الصورة. أنواع مختلفة من المجموعات.

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

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

ضع الصورة. يمكن تجاوز نفس المجموعة بطرق متعددة.

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

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

الحل

الوظيفة الأساسية لنمط المكرر هي استخراج سلوك التجاوز للمجموعة إلى كائن منفصل يسمى المكرِّر (Iterator).

ضع الصورة (تستخدم المكرِّرات خوارزميات تجاوز مختلفة، وتستطيع عدة مكررات أن تتجاوز نفس المجموعة في نفس الوقت).

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

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

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

مثال من الواقع

ضع الصورة. عدة طرق للسير في روما.

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

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

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

البنية

ضع الصورة.

  1. تصرح واجهة المكرر (Iterator) عن العمليات المطلوبة لتخطي مجموعة ما: جلب العنصر التالي، أو استرجاع الخيار الحالي، أو إعادة تشغيل التكرار، إلخ.
  2. تستخدم المكرِّرات الحقيقية (Concrete Iterators) خوارزميات محددة لتخطي مجموعة ما، وينبغي أن يتتبع كائن المكرر تقدُّم عملية التخطي بنفسه، ذلك يسمح لعدة مكررات أن تتخطى نفس المجموعة بشكل مستقل عن بعضها البعض.
  3. تصرح واجهة المجموعة (Collection) عن أسلوب واحد أو أكثر لجعل المكرِّرات تتوافق مع المجموعة، لاحظ أن نوع العودة (return type) للأساليب يجب أن يصرَّح عنه على أنه واجهة المكرِّر (iterator interface) كي تتمكن المجموعات الحقيقية من إعادة أنواع المكرِّرات المختلفة.
  4. تعيد المجموعات الحقيقية (Concrete Collections) نسخًا جديدة من فئة مكرر حقيقي بعينه في كل مرة يطلب العميل واحدًا، وينبغي أن تكون بقية شيفرة المجموعة في نفس الفئة، لكننا نهملها لأن تلك التفاصيل ليست ضرورية للنمط الفعلي.
  5. يعمل العميل (Client) مع المجموعات والمكرِّرات من خلال واجهاتها، وهكذا لا يُربط العميل بالفئات الحقيقية مما يسمح لك باستخدام مجموعات ومكررات متنوعة بنفس شيفرة العميل. ولا تنشئ العملاء عادة مكرِّرات بأنفسها، وإنما تحصل عليها من المجموعات، لكن يستطيع العميل إنشاء مكرر مباشرة في بعض الحالات، كأن يعرِّف العميل المكرِّر الخاص به.

مثال وهمي

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

الصورة. مثال للتكرار على الحسابات الاجتماعية.

يمكن استخدام مكرر "friends" للمرور على الأصدقاء من حساب بعينه، وكذلك مكرر "colleagues" إلا أن الأخير يهمل الأصدقاء الذين لا يعملون في نفس الشركة التي يعمل فيها الشخص الهدف. ويستخدم كلا المكرِّران واجهة مشتركة تسمح للعملاء بجلب الحسابات دون الغوص في تفاصيل الاستخدام كالتصديق (Authentication) وإرسال طلبات REST.

لا تكون شيفرة العميل مرتبطة بالفئات الحقيقية لأنها تعمل مع المجموعات والمكرِّرات من خلال الواجهات فقط، فإن قررت ربط تطبيقك بشبكة اجتماعية جديدة، فلا تحتاج إلا إلى إضافة فئة مجموعة جديدة وفئة مكرِّر دون تغيير الشيفرة الحالية.

// يجب أن تصرِّح واجهة المجموعة عن أسلوب مصنع لإنتاج المكرِّرات.
// تستطيع التصريح عن عدة أساليب إن كان لديك أنواعًا مختلفة من التكرار متاحةً في برنامجك.
// are different kinds of iteration available in your program.
interface SocialNetwork is
    method createFriendsIterator(profileId):ProfileIterator
    method createCoworkersIterator(profileId):ProfileIterator


// تُربَط كل مجموعة حقيقية بمجموعة من فئات المكرِّر الحقيقية التي تعيدها.
// هذه الأساليب يعيد واجهات المكرِّر. (signature) لكن لا ينطبق ذلك على العميل لأن توقيع 
class Facebook implements SocialNetwork is
    // ... توضع شيفرة المجموعة هنا ...

    // شيفرة إنشاء المكرِّر.
    method createFriendsIterator(profileId) is
        return new FacebookIterator(this, profileId, "friends")
    method createCoworkersIterator(profileId) is
        return new FacebookIterator(this, profileId, "coworkers")


// الواجهة المشتركة لكل المكرِّرات.
interface ProfileIterator is
    method getNext():Profile
    method hasMore():bool


// فئة المكرِّر الحقيقي.
class FacebookIterator implements ProfileIterator is
    // يحتاج المكرِّر إلى مرجع إلى المجموعة التي يتخطاها
    private field facebook: Facebook
    private field profileId, type: string

    // يتخطى مكرِّر ما المجموعةَ بشكل مستقل عن باقي المكرِّرات.
    // لذا يجب أن يخزن حالة التكرار.
    private field currentPosition
    private field cache: array of Profile

    constructor FacebookIterator(facebook, profileId, type) is
        this.facebook = facebook
        this.profileId = profileId
        this.type = type

    private method lazyInit() is
        if (cache == null)
            cache = facebook.socialGraphRequest(profileId, type)

    // الخاص بها من واجهة المكرِّر المشتركة (implementation) كل فئة مكرر حقيقي لها تطبيقها.
    method getNext() is
        if (hasMore())
            currentPosition++
            return cache[currentPosition]

    method hasMore() is
        lazyInit()
        return cache.length < currentPosition


// تستطيع تمرير مكرِّرٍ إلى فئة عميل بدلًا من إعطائه وصولًا إلى المجموعة كلها.
// وهكذا تتجنب كشف المجموعة للعميل.
//
// إحدى الفوائد الأخرى كذلك هي أنك تستطيع تغيير الطريقة التي يعمل بها العميل مع المجموعة
// أثناء وقت التشغيل من خلال تمرير مكرر مختلف إليه.
//وهذا ممكن التنفيذ بسبب أن شيفرة العميل ليست مرتبطة بفئات مكرر حقيقية.
class SocialSpammer is
    method send(iterator: ProfileIterator, message: string) is
        while (iterator.hasNext())
            profile = iterator.getNext()
            System.sendEmail(profile.getEmail(), message)


// تهيئ فئة التطبيق المجموعات والمكرِّرات ثم تمررهم إلى شيفرة العميل.
class Application is
    field network: SocialNetwork
    field spammer: SocialSpammer

    method config() is
        if working with Facebook
            this.network = new Facebook()
        if working with LinkedIn
            this.network = new LinkedIn()
        this.spammer = new SocialSpammer()

    method sendSpamToFriends(profile) is
        iterator = network.createFriendsIterator(profile.getId())
        spammer.send(iterator, "Very important message")

    method sendSpamToCoworkers(profile) is
        iterator = network.createCoworkersIterator(profile.getId())
        spammer.send(iterator, "Very important message")

قابلية التطبيق

  • استخدم نمط المكرِّر عندما تكون مجموعتك بها هيكل بيانات معقد وتريد إخفاء ذلك التعقيد عن العملاء (من أجل تسهيل تجربة الاستخدام أو لأسباب أمنية).

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

  • استخدم النمط لتقليل تكرار شيفرات التخطي في برنامجك.

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

  • استخدم المكرِّر حين تريد لشيفرتك أن تكون قادرة على تخطي هياكل بيانات مختلفة أو عند عدم معرفة أنواع من تلك الهياكل مسبقًا.

يوفر النمط بضعة واجهات عامة لكل من المجموعات والمكرِّرات، وبما أن شيفرتك تستخدم الآن هذه الواجهات، فستعمل إن مررت إليها أنواعًا مختلفة من المجموعات والمكرِّرات التي تستخدم تلك الواجهات.

كيفية الاستخدام

  1. صرِّح عن واجهة المكرِّر، يجب أن يكون بها على أسلوبًا واحدًا على الأقل لجلب العنصر التالي من مجموعة، لكن يمكنك إضافة بعض الأساليب الأخرى من أجل التيسير على نفسك، مثل جلب العنصر السابق وتتبع الموضع الحالي وتفقد نهاية التكرار.
  2. صرِّح عن واجهة المجموعة وصِفْ أسلوبًا لجلب المكرِّرات، يجب أن يكون نوع الإعادة (return type) مكافئًا لنوع إعادة واجهة المكرِّر. قد تصرِّح عن أساليب مشابهة إن كنت تخطط أن يكون لديك عدة مجموعات مميزة (distinct) من المكرِّرات.
  3. استخدم فئات المكرر الحقيقي (concrete iterator) للمجموعات التي تريدها أن تكون قابلة للتخطي (traversable) بواسطة المكرِّرات، ويجب أن يكون كائن المكرر مرتبطًا بمجموعة واحدة فقط. عادة ما يُنشأ هذا الرابط من خلال منشئ المكرِّر (iterator's constructor).
  4. استخدم واجهة المجموعة في فئات مجموعتك، غرض هذه الاستخدام هو تزويد العميل باختصار لإنشاء المكرِّرات المصممة من أجل فئة مجموعة بعينها، يجب أن يمرر كائن المجموعة نفسه إلى منشئ المكرِّر من أجل إنشاء رابط بينهما.
  5. اذهب إلى شيفرة العميل لتغيير شيفرة تخطي المجموعة لإحلال المكرِّرات مكانها، سيجلب العميل كائن مكرِّر جديد في كل مرة يحتاج أن يمر فيها على عناصر المجموعة.

المزايا والعيوب

المزايا

مبدأ المسؤولية الواحدة. تستطيع تنفية شيفرة العميل والمجموعات باستخراج خوارزميات التخطي كبيرة الحجم إلى فئات منفصلة.

مبدأ المفتوح/المغلق. تستطيع استخدام أنواع جديدة من المجموعات والمكرِّرات وتمريرها إلى الشيفرة الحالية دون تعطيل أي شيء.

تستطيع المرور على نفس المجموعة بشكل متوازي، ذلك أن كل كائن مكرِّر يحتوي على حالة التكرار الخاصة به.

وأيضًا، لنفس السبب السابق، يمكنك تأخير تكرار واستكماله عند الحاجة.

العيوب

قد يكون من الإسراف استخدام النمط إن كان برنامجك لا يعمل إلا مع المجموعات البسيطة.

قد يكون استخدام النمط أقل كفاءة من المرور على عناصر بعض المجموعات المخصصة مباشرة.

العلاقات مع الأنماط الأخرى

تستطيع استخدام المكرِّرات لتجاوز أشجار نمط المركَّب.

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

تستطيع استخدام نمط التذكرة مع نمط المكرر لتسجيل حالة التكرار الحالية وإرجاعها لحالة سابقة عند الحاجة.

تستطيع استخدام نمط الزائر (Visitor) مع المكرِّر لتجاوز هيكل بيانات معقد وتنفيذ عملية ما على عناصره، حتى لو كانت عناصره كلها تحتوي على فئات مختلفة.

الاستخدام في لغة جافا

الاستخدام في لغة #C

الاستخدام في لغة PHP

الاستخدام في لغة بايثون

الاستخدام في لغة روبي

الاستخدام في لغة Swift

الاستخدام في لغة TypeScript