نمط المكرِّر Iterator
نمط المكرِّر هو نمط تصميم سلوكي يسمح لك بتخطي عناصر من مجموعة (Collection) دون كشف التمثيل التحتي (underlying representation) لها (قائمة، مكدَّس، شجرة، إلخ).
المشكلة
المجموعات هي إحدى أكثر أنواع البيانات استخدامًا في البرمجة، لكن رغم ذلك فهي لا تعدو كونها مجرد حاوية لمجموعة من الكائنات.
ضع الصورة. أنواع مختلفة من المجموعات.
تخزن أغلب المجموعات عناصرها في قوائم بسيطة، لكن تبنى بعضها على مكدسات أو أشجار أو مخططات أو هياكل بيانات معقدة أخرى. لكن بغض النظر عن كيفية هيكلة المجموعة فيجب أن توفر طريقة للوصول إلى عناصرها كي تستطيع الشيفرات الأخرى استخدام تلك العناصر، ويجب أن توجد طريقة للمرور على كل عنصر من المجموعة دون الوصول إلى نفس العناصر أكثر من مرة.
قد يبدو هذا الأمر سهلًا إن كانت مجموعتك مبنية على قائمة، فما عليك سوى المرور على العناصر بالترتيب مرة بعد مرة، لكن كيف تتخطى عناصر تركيب بيانات معقد مثل الشجرة بشكل متسلسل؟ فإن ناسبك تجاوز العمق الأول (depth-first traversal) لشجرة بيانات في يوم ما، فقد لا يناسبك في اليوم التالي إذ قد تحتاج إلى إجراء تجاوز العرض الأول (Breadth-first traversal)، وهكذا بنفس المنطق قد تحتاج شيئًا آخر تمامًا بعد أسبوع مثلًا، كأن تحتاج عناصر بشكل عشوائي داخل الشجرة.
ضع الصورة. يمكن تجاوز نفس المجموعة بطرق متعددة.
ومع الوقت فإن إضافة خورازميات تجاوز إلى المجموعة يشوش وظيفتها الأساسية، والتي هي تخزين البيانات بكفاءة، كذلك فإن بعض الخوارزميات قد تكون مصممة لتطبيق معين، فلا يكون مناسبًا أن تضاف إلى فئة مجموعة عامة.
ومن الناحية الأخرى فإن شيفرة العميل المفترض أن تعمل مع مجموعات متعددة قد لا تهتم أصلًا بالكيفية التي تخزن بها بياناتها، لكن بما أن المجموعات توفر طرقًا مختلفة من الوصول إلى عناصرها، فقد لا يكون لديك خيار سوى ربط شيفرتها بفئات مجموعات محددة.
الحل
الوظيفة الأساسية لنمط المكرر هي استخراج سلوك التجاوز للمجموعة إلى كائن منفصل يسمى المكرِّر (Iterator).
ضع الصورة (تستخدم المكرِّرات خوارزميات تجاوز مختلفة، وتستطيع عدة مكررات أن تتجاوز نفس المجموعة في نفس الوقت).
إضافة إلى استخدام الخوارزمية نفسها، فإن كائن المكرر يختزل كل تفاصيل التجاوز مثل الموضع الحالي وعدد العناصر المتبقية حتى النهاية، ولهذا فتستطيع عدة مكررات أن تمر على مجموعة واحدة في نفس الوقت بشكل مستقل عن بعضها.
وتوفر المكررات عادة أسلوبًا أساسيًا واحدًا للحصول على عناصر مجموعة ما، ويستطيع العميل أن يبقي هذا الأسلوب عاملًا إلى أن لا يعيد أي شيء، ما يعني أن المكرر قد تجاوز كل العناصر.
ويجب أن تستخدم كل المكررات نفس الواجهة، هذا يجعل شيفرة العميل متوافقة مع أي نوع مجموعة أو أي خوارزمية تجاوز طالما أن هناك مكررًا مناسبًا، وإن احتجت أسلوبًا خاصًا لتجاوز مجموعة فأنشئ فئة مكرر جديد ببساطة، دون الحاجة إلى تغيير المجموعة أو العميل.
مثال من الواقع
ضع الصورة. عدة طرق للسير في روما.
لنقل أنك تخطط للذهاب إلى روما في رحلة لزيارة المواقع الأثرية والسياحية، لكنك تكتشف بمجرد وصولك أنك قد تضيع ساعات كثيرة في السير دون هدى، غير قادر على إيجاد أي شيء حتى ساحة الكولوسيوم -أشهر معلم أثري في روما-. لكن من الناحية الأخرى، يمكنك شراء تطبيق للإرشاد السياحي على هاتفك واستخدامه للتنقل، خيار ذكي وغير مكلف، وتستطيع به البقاء في الأماكن الأثرية كما تشاء.
لديك خيار ثالث بديل هنا، وهو أن تنفق مزيدًا من ميزانية الرحلة لتوظف مرشدًا محليًا يعرف المدينة عن ظهر قلب، وسيستطيع ذلك المرشد أن يخصص الرحلة وفقًا لذوقك، ويريك كل موقع جذب سياحي ويخبرك الكثير من القصص المدهشة عن المدينة. لا شك أن هذا سيكون أكثر متعة لكنه مكلف أيضًا.
تتصرف كل تلك الخيارات السابقة، سواء الإرشادات العشوائية أو تطبيق الهاتف أو المرشد المحلي، تتصرف جميعها كمكرِّرات على مجموعة المشاهد ومواقع الجذب السياحي في روما.
البنية
ضع الصورة.
- تصرح واجهة المكرر (Iterator) عن العمليات المطلوبة لتخطي مجموعة ما: جلب العنصر التالي، أو استرجاع الخيار الحالي، أو إعادة تشغيل التكرار، إلخ.
- تستخدم المكرِّرات الحقيقية (Concrete Iterators) خوارزميات محددة لتخطي مجموعة ما، وينبغي أن يتتبع كائن المكرر تقدُّم عملية التخطي بنفسه، ذلك يسمح لعدة مكررات أن تتخطى نفس المجموعة بشكل مستقل عن بعضها البعض.
- تصرح واجهة المجموعة (Collection) عن أسلوب واحد أو أكثر لجعل المكرِّرات تتوافق مع المجموعة، لاحظ أن نوع العودة (return type) للأساليب يجب أن يصرَّح عنه على أنه واجهة المكرِّر (iterator interface) كي تتمكن المجموعات الحقيقية من إعادة أنواع المكرِّرات المختلفة.
- تعيد المجموعات الحقيقية (Concrete Collections) نسخًا جديدة من فئة مكرر حقيقي بعينه في كل مرة يطلب العميل واحدًا، وينبغي أن تكون بقية شيفرة المجموعة في نفس الفئة، لكننا نهملها لأن تلك التفاصيل ليست ضرورية للنمط الفعلي.
- يعمل العميل (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) إلى مكرِّرات محددة يجعل شيفرة البرنامج أكثر رشاقة ونظامًا.
- استخدم المكرِّر حين تريد لشيفرتك أن تكون قادرة على تخطي هياكل بيانات مختلفة أو عند عدم معرفة أنواع من تلك الهياكل مسبقًا.
يوفر النمط بضعة واجهات عامة لكل من المجموعات والمكرِّرات، وبما أن شيفرتك تستخدم الآن هذه الواجهات، فستعمل إن مررت إليها أنواعًا مختلفة من المجموعات والمكرِّرات التي تستخدم تلك الواجهات.
كيفية الاستخدام
- صرِّح عن واجهة المكرِّر، يجب أن يكون بها على أسلوبًا واحدًا على الأقل لجلب العنصر التالي من مجموعة، لكن يمكنك إضافة بعض الأساليب الأخرى من أجل التيسير على نفسك، مثل جلب العنصر السابق وتتبع الموضع الحالي وتفقد نهاية التكرار.
- صرِّح عن واجهة المجموعة وصِفْ أسلوبًا لجلب المكرِّرات، يجب أن يكون نوع الإعادة (return type) مكافئًا لنوع إعادة واجهة المكرِّر. قد تصرِّح عن أساليب مشابهة إن كنت تخطط أن يكون لديك عدة مجموعات مميزة (distinct) من المكرِّرات.
- استخدم فئات المكرر الحقيقي (concrete iterator) للمجموعات التي تريدها أن تكون قابلة للتخطي (traversable) بواسطة المكرِّرات، ويجب أن يكون كائن المكرر مرتبطًا بمجموعة واحدة فقط. عادة ما يُنشأ هذا الرابط من خلال منشئ المكرِّر (iterator's constructor).
- استخدم واجهة المجموعة في فئات مجموعتك، غرض هذه الاستخدام هو تزويد العميل باختصار لإنشاء المكرِّرات المصممة من أجل فئة مجموعة بعينها، يجب أن يمرر كائن المجموعة نفسه إلى منشئ المكرِّر من أجل إنشاء رابط بينهما.
- اذهب إلى شيفرة العميل لتغيير شيفرة تخطي المجموعة لإحلال المكرِّرات مكانها، سيجلب العميل كائن مكرِّر جديد في كل مرة يحتاج أن يمر فيها على عناصر المجموعة.
المزايا والعيوب
المزايا
مبدأ المسؤولية الواحدة. تستطيع تنفية شيفرة العميل والمجموعات باستخراج خوارزميات التخطي كبيرة الحجم إلى فئات منفصلة.
مبدأ المفتوح/المغلق. تستطيع استخدام أنواع جديدة من المجموعات والمكرِّرات وتمريرها إلى الشيفرة الحالية دون تعطيل أي شيء.
تستطيع المرور على نفس المجموعة بشكل متوازي، ذلك أن كل كائن مكرِّر يحتوي على حالة التكرار الخاصة به.
وأيضًا، لنفس السبب السابق، يمكنك تأخير تكرار واستكماله عند الحاجة.
العيوب
قد يكون من الإسراف استخدام النمط إن كان برنامجك لا يعمل إلا مع المجموعات البسيطة.
قد يكون استخدام النمط أقل كفاءة من المرور على عناصر بعض المجموعات المخصصة مباشرة.
العلاقات مع الأنماط الأخرى
- تستطيع استخدام المكرِّرات لتجاوز أشجار نمط المركَّب.
- تستطيع استخدام أسلوب المصنع مع المكرر من أجل السماح للفئات الفرعية لمجموعة ان تعيد أنواعًا مختلفة من المكرِّرات المتوافقة معها.
- تستطيع استخدام نمط التذكرة مع نمط المكرر لتسجيل حالة التكرار الحالية وإرجاعها لحالة سابقة عند الحاجة.
- تستطيع استخدام نمط الزائر (Visitor) مع المكرِّر لتجاوز هيكل بيانات معقد وتنفيذ عملية ما على عناصره، حتى لو كانت عناصره كلها تحتوي على فئات مختلفة.
الاستخدام في لغة جافا
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المكرِّر في لغة جافا إذ تستخدمه أُطر عمل ومكتبات عديدة من أجل توفير طريقة معيارية لتخطي مجموعاتها. إليك بعض الأمثلة من مكتبات جافا:
- كل استخدامات java.util.Iterator (وكذلك java.util.Scanner).
- كل استخدامات java.util.Enumeration.
يمكن التعرف على نمط المكرر بسهولة من خلال أساليب التنقل (مثل next
و previous
وغيرها). قد لا يكون لشيفرة العميل التي تستخدم المكرِّرات وصول إلى المجموعة التي تُتَخطَّى.
التكرار في حسابات الشبكات الاجتماعية
المكرِّرات (Iterators)
iterators/ProfileIterator.java: تُعرِّف واجهة الحساب
package refactoring_guru.iterator.example.iterators;
import refactoring_guru.iterator.example.profile.Profile;
public interface ProfileIterator {
boolean hasNext();
Profile getNext();
void reset();
}
iterators/FacebookIterator.java: تطبق التكرار على حسابات فيس بوك
package refactoring_guru.iterator.example.iterators;
import refactoring_guru.iterator.example.profile.Profile;
import refactoring_guru.iterator.example.social_networks.Facebook;
import java.util.ArrayList;
import java.util.List;
public class FacebookIterator implements ProfileIterator {
private Facebook facebook;
private String type;
private String email;
private int currentPosition = 0;
private List<String> emails = new ArrayList<>();
private List<Profile> profiles = new ArrayList<>();
public FacebookIterator(Facebook facebook, String type, String email) {
this.facebook = facebook;
this.type = type;
this.email = email;
}
private void lazyLoad() {
if (emails.size() == 0) {
List<String> profiles = facebook.requestProfileFriendsFromFacebook(this.email, this.type);
for (String profile : profiles) {
this.emails.add(profile);
this.profiles.add(null);
}
}
}
@Override
public boolean hasNext() {
lazyLoad();
return currentPosition < emails.size();
}
@Override
public Profile getNext() {
if (!hasNext()) {
return null;
}
String friendEmail = emails.get(currentPosition);
Profile friendProfile = profiles.get(currentPosition);
if (friendProfile == null) {
friendProfile = facebook.requestProfileFromFacebook(friendEmail);
profiles.set(currentPosition, friendProfile);
}
currentPosition++;
return friendProfile;
}
@Override
public void reset() {
currentPosition = 0;
}
}
iterators/LinkedInIterator.java: تطبق التكرار على حسابات لينكدإن
package refactoring_guru.iterator.example.iterators;
import refactoring_guru.iterator.example.profile.Profile;
import refactoring_guru.iterator.example.social_networks.LinkedIn;
import java.util.ArrayList;
import java.util.List;
public class LinkedInIterator implements ProfileIterator {
private LinkedIn linkedIn;
private String type;
private String email;
private int currentPosition = 0;
private List<String> emails = new ArrayList<>();
private List<Profile> contacts = new ArrayList<>();
public LinkedInIterator(LinkedIn linkedIn, String type, String email) {
this.linkedIn = linkedIn;
this.type = type;
this.email = email;
}
private void lazyLoad() {
if (emails.size() == 0) {
List<String> profiles = linkedIn.requestRelatedContactsFromLinkedInAPI(this.email, this.type);
for (String profile : profiles) {
this.emails.add(profile);
this.contacts.add(null);
}
}
}
@Override
public boolean hasNext() {
lazyLoad();
return currentPosition < emails.size();
}
@Override
public Profile getNext() {
if (!hasNext()) {
return null;
}
String friendEmail = emails.get(currentPosition);
Profile friendContact = contacts.get(currentPosition);
if (friendContact == null) {
friendContact = linkedIn.requestContactInfoFromLinkedInAPI(friendEmail);
contacts.set(currentPosition, friendContact);
}
currentPosition++;
return friendContact;
}
@Override
public void reset() {
currentPosition = 0;
}
}
الشبكات الاجتماعية (social_networks)
social_networks/SocialNetwork.java: تُعرِّف واجهة مشتركة للشبكة الاجتماعية
package refactoring_guru.iterator.example.social_networks;
import refactoring_guru.iterator.example.iterators.ProfileIterator;
public interface SocialNetwork {
ProfileIterator createFriendsIterator(String profileEmail);
ProfileIterator createCoworkersIterator(String profileEmail);
}
social_networks/Facebook.java: فيس بوك
package refactoring_guru.iterator.example.social_networks;
import refactoring_guru.iterator.example.iterators.FacebookIterator;
import refactoring_guru.iterator.example.iterators.ProfileIterator;
import refactoring_guru.iterator.example.profile.Profile;
import java.util.ArrayList;
import java.util.List;
public class Facebook implements SocialNetwork {
private List<Profile> profiles;
public Facebook(List<Profile> cache) {
if (cache != null) {
this.profiles = cache;
} else {
this.profiles = new ArrayList<>();
}
}
public Profile requestProfileFromFacebook(String profileEmail) {
// الخاص بفيس بوك API إلى إحدى نقاط النهاية للـ POST من المفترض أن يكون هنا طلب.
// لكننا سنحاكي اتصالًا شبكيًا طويلًا بدلًا من ذلك، كما هو الحال في الحالات الواقعية.
simulateNetworkLatency();
System.out.println("Facebook: Loading profile '" + profileEmail + "' over the network...");
// ...ونعيد بيانات اختبار.
return findProfile(profileEmail);
}
public List<String> requestProfileFriendsFromFacebook(String profileEmail, String contactType) {
// الخاص بفيس بوك API إلى إحدى نقاط النهاية للـ POST من المفترض أن يكون هنا طلب.
// لكننا سنحاكي اتصالًا شبكيًا طويلًا بدلًا من ذلك، كما هو الحال في الحالات الواقعية. simulateNetworkLatency();
System.out.println("Facebook: Loading '" + contactType + "' list of '" + profileEmail + "' over the network...");
// ...ونعيد بيانات اختبار.
Profile profile = findProfile(profileEmail);
if (profile != null) {
return profile.getContacts(contactType);
}
return null;
}
private Profile findProfile(String profileEmail) {
for (Profile profile : profiles) {
if (profile.getEmail().equals(profileEmail)) {
return profile;
}
}
return null;
}
private void simulateNetworkLatency() {
try {
Thread.sleep(2500);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
@Override
public ProfileIterator createFriendsIterator(String profileEmail) {
return new FacebookIterator(this, "friends", profileEmail);
}
@Override
public ProfileIterator createCoworkersIterator(String profileEmail) {
return new FacebookIterator(this, "coworkers", profileEmail);
}
}
social_networks/LinkedIn.java: لينكدإن
package refactoring_guru.iterator.example.social_networks;
import refactoring_guru.iterator.example.iterators.LinkedInIterator;
import refactoring_guru.iterator.example.iterators.ProfileIterator;
import refactoring_guru.iterator.example.profile.Profile;
import java.util.ArrayList;
import java.util.List;
public class LinkedIn implements SocialNetwork {
private List<Profile> contacts;
public LinkedIn(List<Profile> cache) {
if (cache != null) {
this.contacts = cache;
} else {
this.contacts = new ArrayList<>();
}
}
public Profile requestContactInfoFromLinkedInAPI(String profileEmail) {
// الخاص بلينكدإن API إلى إحدى نقاط النهاية للـ POST من المفترض أن يكون هنا طلب.
// لكننا سنحاكي اتصالًا شبكيًا طويلًا بدلًا من ذلك، كما هو الحال في الحالات الواقعية. simulateNetworkLatency();
System.out.println("LinkedIn: Loading profile '" + profileEmail + "' over the network...");
// ...ونعيد بيانات اختبار.
return findContact(profileEmail);
}
public List<String> requestRelatedContactsFromLinkedInAPI(String profileEmail, String contactType) {
// الخاص بلينكدإن API إلى إحدى نقاط النهاية للـ POST من المفترض أن يكون هنا طلب.
// لكننا سنحاكي اتصالًا شبكيًا طويلًا بدلًا من ذلك، كما هو الحال في الحالات الواقعية.
simulateNetworkLatency();
System.out.println("LinkedIn: Loading '" + contactType + "' list of '" + profileEmail + "' over the network...");
// ...ونعيد بيانات اختبار.
Profile profile = findContact(profileEmail);
if (profile != null) {
return profile.getContacts(contactType);
}
return null;
}
private Profile findContact(String profileEmail) {
for (Profile profile : contacts) {
if (profile.getEmail().equals(profileEmail)) {
return profile;
}
}
return null;
}
private void simulateNetworkLatency() {
try {
Thread.sleep(2500);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
@Override
public ProfileIterator createFriendsIterator(String profileEmail) {
return new LinkedInIterator(this, "friends", profileEmail);
}
@Override
public ProfileIterator createCoworkersIterator(String profileEmail) {
return new LinkedInIterator(this, "coworkers", profileEmail);
}
}
الحساب الاجتماعي Profile
profile/Profile.java: الحسابات الاجتماعية
package refactoring_guru.iterator.example.profile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Profile {
private String name;
private String email;
private Map<String, List<String>> contacts = new HashMap<>();
public Profile(String email, String name, String... contacts) {
this.email = email;
this.name = name;
// "friend:email@gmail.com" حلل قائمة جهات الاتصال من مجموعة من أزواج
for (String contact : contacts) {
String[] parts = contact.split(":");
String contactType = "friend", contactEmail;
if (parts.length == 1) {
contactEmail = parts[0];
}
else {
contactType = parts[0];
contactEmail = parts[1];
}
if (!this.contacts.containsKey(contactType)) {
this.contacts.put(contactType, new ArrayList<>());
}
this.contacts.get(contactType).add(contactEmail);
}
}
public String getEmail() {
return email;
}
public String getName() {
return name;
}
public List<String> getContacts(String contactType) {
if (!this.contacts.containsKey(contactType)) {
this.contacts.put(contactType, new ArrayList<>());
}
return contacts.get(contactType);
}
}
المزعج spammer
spammer/SocialSpammer.java: برنامج إرسال الرسائل
package refactoring_guru.iterator.example.spammer;
import refactoring_guru.iterator.example.iterators.ProfileIterator;
import refactoring_guru.iterator.example.profile.Profile;
import refactoring_guru.iterator.example.social_networks.SocialNetwork;
public class SocialSpammer {
public SocialNetwork network;
public ProfileIterator iterator;
public SocialSpammer(SocialNetwork network) {
this.network = network;
}
public void sendSpamToFriends(String profileEmail, String message) {
System.out.println("\nIterating over friends...\n");
iterator = network.createFriendsIterator(profileEmail);
while (iterator.hasNext()) {
Profile profile = iterator.getNext();
sendMessage(profile.getEmail(), message);
}
}
public void sendSpamToCoworkers(String profileEmail, String message) {
System.out.println("\nIterating over coworkers...\n");
iterator = network.createCoworkersIterator(profileEmail);
while (iterator.hasNext()) {
Profile profile = iterator.getNext();
sendMessage(profile.getEmail(), message);
}
}
public void sendMessage(String email, String message) {
System.out.println("Sent message to: '" + email + "'. Message body: '" + message + "'");
}
}
Demo.java: شيفرة العميل
package refactoring_guru.iterator.example;
import refactoring_guru.iterator.example.profile.Profile;
import refactoring_guru.iterator.example.social_networks.Facebook;
import refactoring_guru.iterator.example.social_networks.LinkedIn;
import refactoring_guru.iterator.example.social_networks.SocialNetwork;
import refactoring_guru.iterator.example.spammer.SocialSpammer;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* Demo class. Everything comes together here.
*/
public class Demo {
public static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
System.out.println("Please specify social network to target spam tool (default:Facebook):");
System.out.println("1. Facebook");
System.out.println("2. LinkedIn");
String choice = scanner.nextLine();
SocialNetwork network;
if (choice.equals("2")) {
network = new LinkedIn(createTestProfiles());
}
else {
network = new Facebook(createTestProfiles());
}
SocialSpammer spammer = new SocialSpammer(network);
spammer.sendSpamToFriends("anna.smith@bing.com",
"Hey! This is Anna's friend Josh. Can you do me a favor and like this post [link]?");
spammer.sendSpamToCoworkers("anna.smith@bing.com",
"Hey! This is Anna's boss Jason. Anna told me you would be interested in [link].");
}
public static List<Profile> createTestProfiles() {
List<Profile> data = new ArrayList<Profile>();
data.add(new Profile("anna.smith@bing.com", "Anna Smith", "friends:mad_max@ya.com", "friends:catwoman@yahoo.com", "coworkers:sam@amazon.com"));
data.add(new Profile("mad_max@ya.com", "Maximilian", "friends:anna.smith@bing.com", "coworkers:sam@amazon.com"));
data.add(new Profile("bill@microsoft.eu", "Billie", "coworkers:avanger@ukr.net"));
data.add(new Profile("avanger@ukr.net", "John Day", "coworkers:bill@microsoft.eu"));
data.add(new Profile("sam@amazon.com", "Sam Kitting", "coworkers:anna.smith@bing.com", "coworkers:mad_max@ya.com", "friends:catwoman@yahoo.com"));
data.add(new Profile("catwoman@yahoo.com", "Liza", "friends:anna.smith@bing.com", "friends:sam@amazon.com"));
return data;
}
}
OutputDemo.txt: نتائج التنفيذ
Please specify social network to target spam tool (default:Facebook):
1. Facebook
2. LinkedIn
> 1
Iterating over friends...
Facebook: Loading 'friends' list of 'anna.smith@bing.com' over the network...
Facebook: Loading profile 'mad_max@ya.com' over the network...
Sent message to: 'mad_max@ya.com'. Message body: 'Hey! This is Anna's friend Josh. Can you do me a favor and like this post [link]?'
Facebook: Loading profile 'catwoman@yahoo.com' over the network...
Sent message to: 'catwoman@yahoo.com'. Message body: 'Hey! This is Anna's friend Josh. Can you do me a favor and like this post [link]?'
Iterating over coworkers...
Facebook: Loading 'coworkers' list of 'anna.smith@bing.com' over the network...
Facebook: Loading profile 'sam@amazon.com' over the network...
Sent message to: 'sam@amazon.com'. Message body: 'Hey! This is Anna's boss Jason. Anna told me you would be interested in [link].'
الاستخدام في لغة #C
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المكرِّر في لغة #C إذ تستخدمه أُطر عمل ومكتبات عديدة من أجل توفير طريقة معيارية لتخطي مجموعاتها.
يمكن التعرف على نمط المكرر بسهولة من خلال أساليب التنقل (مثل next
و previous
وغيرها). قد لا يكون لشيفرة العميل التي تستخدم المكرِّرات وصول إلى المجموعة التي تُتَخطَّى.
مثال تصوري
يوضح هذا المثال بنية نمط المكرِّر (Iterator)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Program.cs: مثال تصوري
using System;
using System.Collections;
using System.Collections.Generic;
namespace RefactoringGuru.DesignPatterns.Iterator.Conceptual
{
abstract class Iterator : IEnumerator
{
object IEnumerator.Current => Current();
// تعيد مفتاح العنصر الحالي.
public abstract int Key();
// تعيد العنصر الحالي.
public abstract object Current();
// انتقل إلى العنصر التالي.
public abstract bool MoveNext();
// يُرجِع المكرِّر إلى العنصر الأول.
public abstract void Reset();
}
abstract class IteratorAggregate : IEnumerable
{
// آخر لكائن الاستخدام IteratorAggregate يرجع مكرِّرًا أو.
public abstract IEnumerator GetEnumerator();
}
// تستخدم المكررات الحقيقية خوارزميات تخطي متعددة، وتخزن تلك الفئات موضع
// التخطي الحالي بشكل دائم.
class AlphabeticalOrderIterator : Iterator
{
private WordsCollection _collection;
// يخزن هذا المتغير موضع التخطي الحالي. قد يحتوي المكرر على حقول
// كثيرة أخرى لتخزين حالة التكرار، خاصة حين يفترض به
// أن يعمل مع نوع معين من المجموعات.
private int _position = -1;
private bool _reverse = false;
public AlphabeticalOrderIterator(WordsCollection collection, bool reverse = false)
{
this._collection = collection;
this._reverse = reverse;
if (reverse)
{
this._position = collection.getItems().Count;
}
}
public override object Current()
{
return this._collection.getItems()[_position];
}
public override int Key()
{
return this._position;
}
public override bool MoveNext()
{
int updatedPosition = this._position + (this._reverse ? -1 : 1);
if (updatedPosition >= 0 && updatedPosition < this._collection.getItems().Count)
{
this._position = updatedPosition;
return true;
}
else
{
return false;
}
}
public override void Reset()
{
this._position = this._reverse ? this._collection.getItems().Count - 1 : 0;
}
}
// توفر المجموعات الحقيقية أسلوبًا أو أكثر لاسترجاع نسخ مكرِّرات جديدة
// متوافقة مع فئة المجموعة.
class WordsCollection : IteratorAggregate
{
List<string> _collection = new List<string>();
bool _direction = false;
public void ReverseDirection()
{
_direction = !_direction;
}
public List<string> getItems()
{
return _collection;
}
public void AddItem(string item)
{
this._collection.Add(item);
}
public override IEnumerator GetEnumerator()
{
return new AlphabeticalOrderIterator(this, _direction);
}
}
class Program
{
static void Main(string[] args)
{
// من المحتمل أن تدرك شيفرة العميل فئات المكرر الحقيقي أو المجموعة
// أو لا تدركها، وفقًا لمستوى الوضوح الذي تريده لبرنامجك.
var collection = new WordsCollection();
collection.AddItem("First");
collection.AddItem("Second");
collection.AddItem("Third");
Console.WriteLine("Straight traversal:");
foreach (var element in collection)
{
Console.WriteLine(element);
}
Console.WriteLine("\nReverse traversal:");
collection.ReverseDirection();
foreach (var element in collection)
{
Console.WriteLine(element);
}
}
}
}
Output.txt: نتائج التنفيذ
Straight traversal:
First
Second
Third
Reverse traversal:
Third
Second
First
الاستخدام في لغة PHP
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المكرِّر في لغة PHP إذ تستخدمه أُطر عمل ومكتبات عديدة من أجل توفير طريقة معيارية لتخطي مجموعاتها.
تحتوي لغة PHP على واجهة مكرِّر مضمنة بها يمكن استخدامها لبناء مكررات مخصصة متوافقة مع بقية شيفرة PHP.
مثال تصوري
يوضح هذا المثال بنية نمط المكرِّر (Iterator)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP.
index.php: مثال تصوري
<?php
namespace RefactoringGuru\Iterator\Conceptual;
/**
* تستخدم المكررات الحقيقية خوارزميات تخطي متعددة، وتخزن تلك الفئات موضع
* التخطي الحالي بشكل دائم.
*/
class AlphabeticalOrderIterator implements \Iterator
{
/**
* @var WordsCollection
*/
private $collection;
/**
* @var int
* يخزن هذا المتغير موضع التخطي الحالي. قد يحتوي المكرر على حقول
* كثيرة أخرى لتخزين حالة التكرار، خاصة حين يفترض به
* أن يعمل مع نوع معين من المجموعات.
*/
private $position = 0;
/**
* @var bool
* يوضح هذا المتغير اتجاه التخطي.
*/
private $reverse = false;
public function __construct($collection, $reverse = false)
{
$this->collection = $collection;
$this->reverse = $reverse;
}
public function rewind()
{
$this->position = $this->reverse ?
count($this->collection->getItems()) - 1 : 0;
}
public function current()
{
return $this->collection->getItems()[$this->position];
}
public function key()
{
return $this->position;
}
public function next()
{
$this->position = $this->position + ($this->reverse ? -1 : 1);
}
public function valid()
{
return isset($this->collection->getItems()[$this->position]);
}
}
/**
* توفر المجموعات الحقيقية أسلوبًا أو أكثر لاسترجاع نسخ مكرِّرات جديدة
* متوافقة مع فئة المجموعة.
*/
class WordsCollection implements \IteratorAggregate
{
private $items = [];
public function getItems()
{
return $this->items;
}
public function addItem($item)
{
$this->items[] = $item;
}
public function getIterator(): Iterator
{
return new AlphabeticalOrderIterator($this);
}
public function getReverseIterator(): Iterator
{
return new AlphabeticalOrderIterator($this, true);
}
}
/**
* من المحتمل أن تدرك شيفرة العميل فئات المكرر الحقيقي أو المجموعة
* أو لا تدركها، وفقًا لمستوى الوضوح الذي تريده لبرنامجك.
*/
$collection = new WordsCollection;
$collection->addItem("First");
$collection->addItem("Second");
$collection->addItem("Third");
echo "Straight traversal:\n";
foreach ($collection->getIterator() as $item) {
echo $item . "\n";
}
echo "\n";
echo "Reverse traversal:\n";
foreach ($collection->getReverseIterator() as $item) {
echo $item . "\n";
}
Output.txt: نتائج التنفيذ
Straight traversal:
First
Second
Third
Reverse traversal:
Third
Second
First
مثال واقعي
بما أن لغة PHP بها واجهة مكرر مضمنة بها تقدم تكاملًا مع حلقات foreach
التكرارية، فمن السهل إنشاء المكررات الخاصة بك لتخطي أي هيكل بيانات تقريبًا. يقدم المثال التالي لنمط المكرِّر وصولًا سهلًا لملفات CSV.
index.php: مثال واقعي
<?php
namespace RefactoringGuru\Iterator\RealWorld;
/**
* CSV مكرِّر ملف.
*
* @author Josh Lockhart
*/
class CsvIterator implements \Iterator
{
const ROW_SIZE = 4096;
/**
* CSV المؤشر إلى ملف الـ.
*
* @var resource
*/
protected $filePointer = null;
/**
* العنصر الحالي الذي يعاد في كل عملية تكرار.
*
* @var array
*/
protected $currentElement = null;
/**
* عداد الصفوف.
*
* @var int
*/
protected $rowCounter = null;
/**
* CSV محدِّد ملف الـ.
*
* @var string
*/
protected $delimiter = null;
/**
* ويعطي استثناءً عند الفشل ،CSV يحاول المنشئ فتح ملف الـ
*
* @param string $file ، CSVملف الـ.
* @param string $delimiter المحدِّد.
*
* @throws \Exception
*/
public function __construct($file, $delimiter = ',')
{
try {
$this->filePointer = fopen($file, 'rb');
$this->delimiter = $delimiter;
} catch (\Exception $e) {
throw new \Exception('The file "' . $file . '" cannot be read.');
}
}
/**
* يعيد هذا الأسلوب ضبط مؤشر الملف.
*/
public function rewind(): void
{
$this->rowCounter = 0;
rewind($this->filePointer);
}
/**
* الحالي كمصفوفة ثنائية الأبعاد CSVيعيد هذا الأسلوبُ صفَّ الـ.
*
* @return array الحالي الحالي كمصفوفة ثنائية الأبعاد CSVصف الـ.
*/
public function current(): array
{
$this->currentElement = fgetcsv($this->filePointer, self::ROW_SIZE, $this->delimiter);
$this->rowCounter++;
return $this->currentElement;
}
/**
* هذا الأسلوب يعيد رقم الصف الحالي.
*
* @return int رقم الصف الحالي.
*/
public function key(): int
{
return $this->rowCounter;
}
/**
* يتفقد هذا الأسلوب إن تم الوصول إلى نهاية الملف.
*
* @return bool في غير ذلك false عند الوصول إلى نهاية الملف، ويعيد true يعيد.
*/
public function next(): bool
{
if (is_resource($this->filePointer)) {
return !feof($this->filePointer);
}
return false;
}
/**
* يتفقد هذا الأسلوب إن كان الصف التالي صفًا صالحًا.
*
* @return bool إن كان الصف التالي صفًا صالحًا.
*/
public function valid(): bool
{
if (!$this->next()) {
if (is_resource($this->filePointer)) {
fclose($this->filePointer);
}
return false;
}
return true;
}
}
/**
* شيفرة العميل.
*/
$csv = new CsvIterator(__DIR__ . '/cats.csv');
foreach ($csv as $key => $row) {
print_r($row);
}
Output.txt: نتائج التنفيذ
Array
(
[0] => Name
[1] => Age
[2] => Owner
[3] => Breed
[4] => Image
[5] => Color
[6] => Texture
[7] => Fur
[8] => Size
)
Array
(
[0] => Steve
[1] => 3
[2] => Alexander Shvets
[3] => Bengal
[4] => /cats/bengal.jpg
[5] => Brown
[6] => Stripes
[7] => Short
[8] => Medium
)
Array
(
[0] => Siri
[1] => 2
[2] => Alexander Shvets
[3] => Domestic short-haired
[4] => /cats/domestic-sh.jpg
[5] => Black
[6] => Solid
[7] => Medium
[8] => Medium
)
Array
(
[0] => Fluffy
[1] => 5
[2] => John Smith
[3] => Maine Coon
[4] => /cats/Maine-Coon.jpg
[5] => Gray
[6] => Stripes
[7] => Long
[8] => Large
)
الاستخدام في لغة بايثون
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المكرِّر في لغة بايثون إذ تستخدمه أُطر عمل ومكتبات عديدة من أجل توفير طريقة معيارية لتخطي مجموعاتها.
يمكن التعرف على نمط المكرر بسهولة من خلال أساليب التنقل (مثل next
و previous
وغيرها). قد لا يكون لشيفرة العميل التي تستخدم المكرِّرات وصول إلى المجموعة التي تُتَخطَّى.
مثال تصوري
يوضح هذا المثال بنية نمط المكرِّر (Iterator)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.py: مثال تصوري
from __future__ import annotations
from collections.abc import Iterable, Iterator
from typing import Any, List
"""
المضمنة في لغة بايثون، ويمكننا استخدامهما لإنشاء "Collection" توجد فئتان مجردتان من وحدة
Iterator و Iterable مكرر في هذه اللغة، وهاتان الفئتان هما.
في المكرِّر نفسه __next__() في الكائن المكرَّر، وأسلوب __iter__() وسنحتاج أن نستخدم أسلوب.
"""
class AlphabeticalOrderIterator(Iterator):
"""
تستخدم المكررات الحقيقية خوارزميات تخطي متعددة، وتخزن تلك الفئات
موضع التخطي الحالي بشكل دائم.
"""
"""
موضع التخطي الحالي. قد يحتوي المكرِّر على حقول أخرى _position يخزن وسيط
كثيرة لتخزين حالة التكرار، خاصة حين يفترض به أن يعمل مع نوع معين
من المجموعات.
"""
_position: int = None
"""
يوضح هذا الوسيط اتجاه التخطي.
"""
_reverse: bool = False
def __init__(self, collection: WordsCollection, reverse: bool = False) -> None:
self._collection = collection
self._reverse = reverse
self._position = -1 if reverse else 0
def __next__(self):
"""
العنصرَ التالي في التسلسل __next__() ُيجب أن يعيد أسلوب.
عند الوصول للنهاية وفي StopIteration ويجب أيضًا أن يرفع استثناء
الاستدعاءات اللاحقة.
"""
try:
value = self._collection[self._position]
self._position += -1 if self._reverse else 1
except IndexError:
raise StopIteration()
return value
class WordsCollection(Iterable):
"""
توفر المجموعات الحقيقية أسلوبًا أو أكثر لاسترجاع نسخ مكرِّرات جديدة
متوافقة مع فئة المجموعة.
"""
def __init__(self, collection: List[Any] = []) -> None:
self._collection = collection
def __iter__(self) -> AlphabeticalOrderIterator:
"""
كائن المكرِّر نفسه، وافتراضيًا، فإننا نعيد المكرر في __iter__() يعيد أسلوب
ترتيب تصاعدي.
"""
return AlphabeticalOrderIterator(self._collection)
def get_reverse_iterator(self) -> AlphabeticalOrderIterator:
return AlphabeticalOrderIterator(self._collection, True)
def add_item(self, item: Any):
self._collection.append(item)
if __name__ == "__main__":
# من المحتمل أن تدرك شيفرة العميل فئات المكرر الحقيقي أو المجموعة
# أو لا تدركها، وفقًا لمستوى الوضوح الذي تريده لبرنامجك.
collection = WordsCollection()
collection.add_item("First")
collection.add_item("Second")
collection.add_item("Third")
print("Straight traversal:")
print("\n".join(collection))
print("")
print("Reverse traversal:")
print("\n".join(collection.get_reverse_iterator()), end="")
Output.txt: نتائج التنفيذ
Straight traversal:
First
Second
Third
Reverse traversal:
Third
Second
First
الاستخدام في لغة روبي
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المكرِّر في لغة جافا إذ تستخدمه أُطر عمل ومكتبات عديدة من أجل توفير طريقة معيارية لتخطي مجموعاتها.
يمكن التعرف على نمط المكرر بسهولة من خلال أساليب التنقل (مثل next
و previous
وغيرها). قد لا يكون لشيفرة العميل التي تستخدم المكرِّرات وصول إلى المجموعة التي تُتَخطَّى.
مثال تصوري
يوضح هذا المثال بنية نمط المكرِّر (Iterator)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.rb: مثال تصوري
class AlphabeticalOrderIterator
# الفئات بعدة أساليب (Enumerable mixin) يزود المزيج القابل للإحصاء في روبي
# (Sorting) تخطي وبحث، وبقابلية التصنيف أيضًا.
# ويجب أن توفر الفئة أسلوبًا يعيد العناصر التالية في المجموعة.
include Enumerable
# يوضح هذا الوسيط اتجاه التخطي.
attr_accessor :reverse
private :reverse
# @return [Array]
attr_accessor :collection
private :collection
# @param [Array] collection
# @param [Boolean] reverse
def initialize(collection, reverse = false)
@collection = collection
@reverse = reverse
end
def each(&block)
return @collection.reverse.each(&block) if reverse
@collection.each(&block)
end
end
class WordsCollection
# @return [Array]
attr_accessor :collection
private :collection
def initialize(collection = [])
@collection = collection
end
# كائن المكرِّر نفسه. نحن نعيد المكرر في iterator يعيد أسلوب
# ترتيب تصاعدي افتراضيًا.
def iterator
AlphabeticalOrderIterator.new(@collection)
end
# @return [AlphabeticalOrderIterator]
def reverse_iterator
AlphabeticalOrderIterator.new(@collection, true)
end
# @param [String] item
def add_item(item)
@collection << item
end
end
# من المحتمل أن تدرك شيفرة العميل فئات المكرر الحقيقي أو المجموعة
# أو لا تدركها، وفقًا لمستوى الوضوح الذي تريده لبرنامجك.
collection = WordsCollection.new
collection.add_item('First')
collection.add_item('Second')
collection.add_item('Third')
puts 'Straight traversal:'
collection.iterator.each { |item| puts item }
puts "\n"
puts 'Reverse traversal:'
collection.reverse_iterator.each { |item| puts item }
Output.txt: نتائج التنفيذ
Straight traversal:
First
Second
Third
Reverse traversal:
Third
Second
First
الاستخدام في لغة Swift
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المكرِّر في لغة جافا إذ تستخدمه أُطر عمل ومكتبات عديدة من أجل توفير طريقة معيارية لتخطي مجموعاتها.
يمكن التعرف على نمط المكرر بسهولة من خلال أساليب التنقل (مثل next
و previous
وغيرها). قد لا يكون لشيفرة العميل التي تستخدم المكرِّرات وصول إلى المجموعة التي تُتَخطَّى.
مثال تصوري
يوضح هذا المثال بنية نمط المكرِّر (Iterator)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Example.swift: مثال تصوري
import XCTest
/// IteratorProtocol هذه مجموعة سنكررها باستخدام مكرِّر مشتق من.
class WordsCollection {
fileprivate lazy var items = [String]()
func append(_ item: String) {
self.items.append(item)
}
}
extension WordsCollection: Sequence {
func makeIterator() -> WordsIterator {
return WordsIterator(self)
}
}
/// تستخدم المكررات الحقيقية خوارزميات تخطي متعددة، وتخزن تلك
/// الفئات موضع التخطي الحالي بشكل دائم.
class WordsIterator: IteratorProtocol {
private let collection: WordsCollection
private var index = 0
init(_ collection: WordsCollection) {
self.collection = collection
}
func next() -> String? {
defer { index += 1 }
return index < collection.items.count ? collection.items[index] : nil
}
}
/// لتخطي عناصرها AnyIterator هذه مجموعة أخرى سنزودها بـ.
class NumbersCollection {
fileprivate lazy var items = [Int]()
func append(_ item: Int) {
self.items.append(item)
}
}
extension NumbersCollection: Sequence {
func makeIterator() -> AnyIterator<Int> {
var index = self.items.count - 1
return AnyIterator {
defer { index -= 1 }
return index >= 0 ? self.items[index] : nil
}
}
}
/// لا تعرف شيفرة العميل التمثيلَ الداخلي لأي تسلسل مُعطى.
class Client {
// ...
static func clientCode<S: Sequence>(sequence: S) {
for item in sequence {
print(item)
}
}
// ...
}
/// لنرى كيف سيعمل كل هذا معًا.
class IteratorConceptual: XCTestCase {
func testIteratorProtocol() {
let words = WordsCollection()
words.append("First")
words.append("Second")
words.append("Third")
print("Straight traversal using IteratorProtocol:")
Client.clientCode(sequence: words)
}
func testAnyIterator() {
let numbers = NumbersCollection()
numbers.append(1)
numbers.append(2)
numbers.append(3)
print("\nReverse traversal using AnyIterator:")
Client.clientCode(sequence: numbers)
}
}
Output.txt: نتائج التنفيذ
Straight traversal:
First
Second
Third
Reverse traversal:
3
2
1
مثال واقعي
Example.swift: مثال واقعي
import XCTest
class IteratorRealWorld: XCTestCase {
func test() {
let tree = Tree(1)
tree.left = Tree(2)
tree.right = Tree(3)
print("Tree traversal: Inorder")
clientCode(iterator: tree.iterator(.inOrder))
print("\nTree traversal: Preorder")
clientCode(iterator: tree.iterator(.preOrder))
print("\nTree traversal: Postorder")
clientCode(iterator: tree.iterator(.postOrder))
}
func clientCode<T>(iterator: AnyIterator<T>) {
while case let item? = iterator.next() {
print(item)
}
}
}
class Tree<T> {
var value: T
var left: Tree<T>?
var right: Tree<T>?
init(_ value: T) {
self.value = value
}
typealias Block = (T) -> ()
enum IterationType {
case inOrder
case preOrder
case postOrder
}
func iterator(_ type: IterationType) -> AnyIterator<T> {
var items = [T]()
switch type {
case .inOrder:
inOrder { items.append($0) }
case .preOrder:
preOrder { items.append($0) }
case .postOrder:
postOrder { items.append($0) }
}
/// لاحظ:
/// (Type Signature) لإخفاء توقيع النوع AnyIterator يستخدم
/// عن المكرر الحقيقي.
return AnyIterator(items.makeIterator())
}
private func inOrder(_ body: Block) {
left?.inOrder(body)
body(value)
right?.inOrder(body)
}
private func preOrder(_ body: Block) {
body(value)
left?.inOrder(body)
right?.inOrder(body)
}
private func postOrder(_ body: Block) {
left?.inOrder(body)
right?.inOrder(body)
body(value)
}
}
Output.txt: نتائج التنفيذ
Tree traversal: Inorder
2
1
3
Tree traversal: Preorder
1
2
3
Tree traversal: Postorder
2
3
1
الاستخدام في لغة TypeScript
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المكرِّر في لغة جافا إذ تستخدمه أُطر عمل ومكتبات عديدة من أجل توفير طريقة معيارية لتخطي مجموعاتها.
يمكن التعرف على نمط المكرر بسهولة من خلال أساليب التنقل (مثل next
و previous
وغيرها). قد لا يكون لشيفرة العميل التي تستخدم المكرِّرات وصول إلى المجموعة التي تُتَخطَّى.
مثال تصوري
يوضح هذا المثال بنية نمط المكرِّر (Iterator)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
index.ts: مثال تصوري
/**
* نمط تصميم المكرِّر
*
* الهدف: يسمح لك بتخطي عناصر المجموعة دون كشف التمثيل
* التحتي لها، سواء كان التمثيل شجرة أو مكدَّسًا أو قائمة أو غير ذلك.
*/
interface Iterator<T> {
// يعيد العنصر الحالي.
current(): any;
// يعيد العنصر الحالي ويمضي قدمًا إلى العنصر التالي.
next(): T;
// يعيد مفتاح العنصر الحالي.
key(): number;
// يتحقق إن كان الموضع الحالي صالحًا.
valid(): boolean;
// يٌرجع المكرر إلى العنصر الأول.
rewind(): void;
}
interface Aggregator {
// يسترجع مكررًا خارجيًا.
getIterator(): Iterator<string>;
}
/**
* تستخدم المكررات الحقيقية خوارزميات تخطي متعددة، وتخزن تلك الفئات
* موضع التخطي الحالي بشكل دائم.
*/
class AlphabeticalOrderIterator implements Iterator<string> {
private collection: WordsCollection;
/**
* يخزن هذا المتغير موضع التخطي الحالي. قد يحتوي المكرر على حقول
* كثيرة أخرى لتخزين حالة التكرار، خاصة حين يفترض به
* أن يعمل مع نوع معين من المجموعات.
*/
private position: number = 0;
/**
* يوضح هذا المتغير اتجاه التخطي.
*/
private reverse: boolean = false;
constructor(collection: WordsCollection, reverse: boolean = false) {
this.collection = collection;
this.reverse = reverse;
if (reverse) {
this.position = collection.getCount() - 1;
}
}
public rewind() {
this.position = this.reverse ?
this.collection.getCount() - 1 :
0;
}
public current(): any {
return this.collection.getItems()[this.position];
}
public key(): number {
return this.position;
}
public next(): any {
const item = this.collection.getItems()[this.position];
this.position += this.reverse ? -1 : 1;
return item;
}
public valid(): boolean {
if (this.reverse) {
return this.position >= 0;
}
return this.position < this.collection.getCount();
}
}
/**
* توفر المجموعات الحقيقية أسلوبًا أو أكثر لاسترجاع نسخ مكرِّرات جديدة
* متوافقة مع فئة المجموعة.
*/
class WordsCollection implements Aggregator {
private items: string[] = [];
public getItems(): string[] {
return this.items;
}
public getCount(): number {
return this.items.length;
}
public addItem(item: string): void {
this.items.push(item);
}
public getIterator(): Iterator<string> {
return new AlphabeticalOrderIterator(this);
}
public getReverseIterator(): Iterator<string> {
return new AlphabeticalOrderIterator(this, true);
}
}
/**
* من المحتمل أن تدرك شيفرة العميل فئات المكرر الحقيقي أو المجموعة
* أو لا تدركها، وفقًا لمستوى الوضوح الذي تريده لبرنامجك.
*/
const collection = new WordsCollection();
collection.addItem('First');
collection.addItem('Second');
collection.addItem('Third');
const iterator = collection.getIterator();
console.log('Straight traversal:');
while (iterator.valid()) {
console.log(iterator.next());
}
console.log('');
console.log('Reverse traversal:');
const reverseIterator = collection.getReverseIterator();
while (reverseIterator.valid()) {
console.log(reverseIterator.next());
}
Output.txt: نتائج التنفيذ
Straight traversal:
First
Second
Third
Reverse traversal:
Third
Second
First