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

##Problem
تُعدّ المجموعات من أكثر أنواع البيانات استخداماً في البرمجة. ومع ذلك، فالمجموعة ليست سوى حاوية لمجموعة من الكائنات.

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

يمكن اجتياز نفس المجموعة بعدة طرق مختلفة.
إن إضافة المزيد والمزيد من خوارزميات الاجتياز إلى المجموعة يُضعف تدريجياً مسؤوليتها الأساسية، وهي تخزين البيانات بكفاءة. علاوة على ذلك، قد تكون بعض الخوارزميات مُصمَّمة لتطبيق معين، مما يجعل تضمينها في فئة مجموعة عامة أمراً غريباً.
من ناحية أخرى، قد لا يهتم كود العميل المفترض للعمل مع مجموعات مختلفة بكيفية تخزينها لعناصرها. غير أن المجموعات جميعها توفر طرقاً مختلفة للوصول إلى عناصرها، لذا لا يكون أمامك خيار سوى ربط كودك بفئات المجموعة المحددة.
##Solution
الفكرة الأساسية لنمط المُكرِّر هي استخراج سلوك اجتياز المجموعة إلى كائن منفصل يُسمى المُكرِّر.

تُنفِّذ المُكرِّرات خوارزميات اجتياز متنوعة. يمكن لعدة كائنات مُكرِّر اجتياز نفس المجموعة في الوقت ذاته.
إضافةً إلى تنفيذ الخوارزمية نفسها، يُغلِّف كائن المُكرِّر جميع تفاصيل الاجتياز، مثل الموضع الحالي وعدد العناصر المتبقية حتى النهاية. بفضل ذلك، يمكن لعدة مُكرِّرات اجتياز نفس المجموعة في وقت واحد باستقلالية تامة عن بعضها البعض.
عادةً، توفر المُكرِّرات طريقة رئيسية واحدة لجلب عناصر المجموعة. يمكن للعميل الاستمرار في تشغيل هذه الطريقة حتى لا تُعيد شيئاً، مما يعني أن المُكرِّر قد اجتاز جميع العناصر.
يجب على جميع المُكرِّرات تنفيذ الواجهة ذاتها. يجعل ذلك كود العميل متوافقاً مع أي نوع مجموعة أو أي خوارزمية اجتياز طالما يوجد مُكرِّر مناسب. وإذا احتجت إلى طريقة خاصة لاجتياز مجموعة، فأنت تنشئ فئة مُكرِّر جديدة فقط دون الحاجة إلى تغيير المجموعة أو العميل.
##Structure
-
تُعلِن واجهة المُكرِّر عن العمليات المطلوبة لاجتياز المجموعة: جلب العنصر التالي، واسترجاع الموضع الحالي، وإعادة تشغيل التكرار، إلخ.
-
تُنفِّذ المُكرِّرات المُحدَّدة خوارزميات محددة لاجتياز المجموعة. يجب على كائن المُكرِّر تتبع تقدم الاجتياز من تلقاء نفسه. يسمح ذلك لعدة مُكرِّرات باجتياز نفس المجموعة باستقلالية عن بعضها البعض.
-
تُعلِن واجهة المجموعة عن طريقة أو أكثر للحصول على مُكرِّرات متوافقة مع المجموعة. لاحظ أنه يجب إعلان نوع القيمة المُعادة من الطرق كواجهة المُكرِّر حتى تتمكن المجموعات المُحدَّدة من إرجاع أنواع متنوعة من المُكرِّرات.
-
تُعيد المجموعات المُحدَّدة نسخاً جديدة من فئة مُكرِّر مُحدَّد معينة في كل مرة يطلبها العميل. قد تتساءل أين بقية كود المجموعة؟ لا تقلق، يجب أن يكون في نفس الفئة. الأمر فقط أن هذه التفاصيل ليست ضرورية للنمط الفعلي، لذا نحذفها.
-
يعمل العميل مع المجموعات والمُكرِّرات عبر واجهاتها. بهذه الطريقة لا يرتبط العميل بالفئات المُحدَّدة، مما يتيح لك استخدام مجموعات ومُكرِّرات متنوعة مع نفس كود العميل.
عادةً، لا ينشئ العملاء المُكرِّرات بأنفسهم، بل يحصلون عليها من المجموعات. ولكن في حالات معينة، يمكن للعميل إنشاء مُكرِّر مباشرةً؛ على سبيل المثال، عندما يعرِّف العميل مُكرِّره الخاص المميز.
##Pseudocode
في هذا المثال، يُستخدَم نمط المُكرِّر للتنقل عبر نوع خاص من المجموعات التي تُغلِّف الوصول إلى الرسم البياني الاجتماعي لـ Facebook. توفر المجموعة عدة مُكرِّرات يمكنها اجتياز الملفات الشخصية بطرق متنوعة.

مثال على التكرار عبر ملفات اجتماعية شخصية.
يمكن استخدام مُكرِّر ‘friends’ للتنقل عبر أصدقاء ملف شخصي معين. أما مُكرِّر ‘colleagues’ فيفعل الشيء نفسه، باستثناء أنه يتجاوز الأصدقاء الذين لا يعملون في نفس الشركة مثل الشخص المستهدف. كلا المُكرِّرَين يُنفِّذان واجهة مشتركة تتيح للعملاء جلب الملفات الشخصية دون الخوض في تفاصيل التنفيذ مثل المصادقة وإرسال طلبات REST.
لا يرتبط كود العميل بالفئات المُحدَّدة لأنه يعمل مع المجموعات والمُكرِّرات فقط من خلال الواجهات. إذا قررت ربط تطبيقك بشبكة اجتماعية جديدة، فأنت تحتاج فقط إلى توفير فئات مجموعة ومُكرِّر جديدة دون تغيير الكود الموجود.
// يجب أن تُعلِن واجهة المجموعة عن أسلوب مصنع
// لإنتاج المُكرِّرات. يمكنك إعلان عدة طرق إذا كانت
// هناك أنواع مختلفة من التكرار متاحة في برنامجك.
interface SocialNetwork is
method createFriendsIterator(profileId):ProfileIterator
method createCoworkersIterator(profileId):ProfileIterator
// كل مجموعة مُحدَّدة مرتبطة بمجموعة من فئات المُكرِّرات
// المُحدَّدة التي تُعيدها. لكن العميل ليس كذلك، إذ
// توقيع هذه الطرق يُعيد واجهات المُكرِّر.
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)
// كل فئة مُكرِّر مُحدَّدة لها تنفيذها الخاص
// لواجهة المُكرِّر المشتركة.
method getNext() is
if (hasMore())
result = cache[currentPosition]
currentPosition++
return result
method hasMore() is
lazyInit()
return currentPosition < cache.length
// إليك حيلة أخرى مفيدة: يمكنك تمرير مُكرِّر إلى
// فئة عميل بدلاً من منحه وصولاً إلى مجموعة
// كاملة. بهذه الطريقة، لا تكشف المجموعة للعميل.
//
// وهناك فائدة أخرى: يمكنك تغيير طريقة عمل
// العميل مع المجموعة أثناء التشغيل عن طريق تمريره
// مُكرِّراً مختلفاً. وهذا ممكن لأن كود العميل
// ليس مرتبطاً بفئات المُكرِّر المُحدَّدة.
class SocialSpammer is
method send(iterator: ProfileIterator, message: string) is
while (iterator.hasMore())
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")
##Applicability
استخدم نمط المُكرِّر عندما تحتوي مجموعتك على بنية بيانات معقدة في الخلفية، لكنك تريد إخفاء هذا التعقيد عن العملاء (سواءً لأسباب تتعلق بالراحة أو الأمان).
يُغلِّف المُكرِّر تفاصيل العمل مع بنية البيانات المعقدة، مما يوفر للعميل عدة طرق بسيطة للوصول إلى عناصر المجموعة. وفي حين أن هذا النهج مريح جداً للعميل، فإنه يحمي أيضاً المجموعة من الإجراءات المتهورة أو الخبيثة التي قد يقوم بها العميل لو عمل مع المجموعة مباشرةً.
استخدم النمط للحد من تكرار كود الاجتياز في تطبيقك.
يميل كود خوارزميات التكرار غير البديهية إلى أن يكون ضخماً جداً. وعند وضعه داخل منطق الأعمال في التطبيق، قد يُعتِّم على مسؤولية الكود الأصلي ويجعله أصعب في الصيانة. ونقل كود الاجتياز إلى مُكرِّرات مخصصة يساعدك على جعل كود التطبيق أكثر إيجازاً ونظافة.
استخدم نمط المُكرِّر عندما تريد لكودك القدرة على اجتياز هياكل بيانات مختلفة أو عندما تكون أنواع هذه الهياكل غير معروفة مسبقاً.
يوفر النمط واجهتين عامتين للمجموعات والمُكرِّرات على حد سواء. وبما أن كودك يستخدم هذه الواجهات الآن، فسيستمر في العمل إذا مررت له أنواعاً مختلفة من المجموعات والمُكرِّرات التي تُنفِّذ هذه الواجهات.
##How to Implement
-
أعلِن عن واجهة المُكرِّر. على أقل تقدير، يجب أن تحتوي على طريقة لجلب العنصر التالي من المجموعة. لكن من أجل الراحة يمكنك إضافة بعض الطرق الأخرى، مثل جلب العنصر السابق وتتبع الموضع الحالي والتحقق من نهاية التكرار.
-
أعلِن عن واجهة المجموعة وصِف فيها طريقة لجلب المُكرِّرات. يجب أن يكون نوع القيمة المُعادة مماثلاً لنوع واجهة المُكرِّر. يمكنك إعلان طرق مماثلة إذا كنت تخطط لامتلاك عدة مجموعات متميزة من المُكرِّرات.
-
نفِّذ فئات مُكرِّر مُحدَّدة للمجموعات التي تريد اجتيازها بالمُكرِّرات. يجب أن يكون كائن المُكرِّر مرتبطاً بنسخة واحدة من المجموعة. عادةً، يتم إنشاء هذا الارتباط عبر مُنشئ المُكرِّر.
-
نفِّذ واجهة المجموعة في فئات المجموعة الخاصة بك. الفكرة الأساسية هي توفير اختصار للعميل لإنشاء المُكرِّرات المُصمَّمة لفئة مجموعة معينة. يجب أن يمرر كائن المجموعة نفسه إلى مُنشئ المُكرِّر لإنشاء رابط بينهما.
-
راجع كود العميل واستبدل كل كود اجتياز المجموعة باستخدام المُكرِّرات. يجلب العميل كائن مُكرِّر جديداً في كل مرة يحتاج فيها إلى التكرار على عناصر المجموعة.
##Pros & Cons
- مبدأ المسؤولية الفردية. يمكنك تنظيف كود العميل والمجموعات عن طريق استخراج خوارزميات الاجتياز الضخمة في فئات منفصلة.
- مبدأ الفتح/الإغلاق. يمكنك تنفيذ أنواع جديدة من المجموعات والمُكرِّرات وتمريرها إلى الكود الموجود دون كسر أي شيء.
- يمكنك التكرار على نفس المجموعة بالتوازي لأن كل كائن مُكرِّر يحتوي على حالة التكرار الخاصة به.
- لنفس السبب، يمكنك تأجيل عملية التكرار والاستمرار فيها عند الحاجة.
- قد يكون تطبيق النمط مبالغاً فيه إذا كان تطبيقك يعمل فقط مع مجموعات بسيطة.
- قد يكون استخدام مُكرِّر أقل كفاءة من الوصول المباشر إلى عناصر بعض المجموعات المتخصصة.
##Relations with Other Patterns
-
يمكنك استخدام المُكرِّرات لاجتياز أشجار المُرَكَّب.
-
يمكنك استخدام أسلوب المصنع مع المُكرِّر للسماح للفئات الفرعية للمجموعات بإرجاع أنواع مختلفة من المُكرِّرات المتوافقة مع المجموعات.
-
يمكنك استخدام الميمنتو مع المُكرِّر لالتقاط حالة التكرار الحالية والتراجع عنها عند الضرورة.
-
يمكنك استخدام الزائر مع المُكرِّر لاجتياز بنية بيانات معقدة وتنفيذ عملية على عناصرها، حتى لو كانت جميعها تنتمي إلى فئات مختلفة.