نمط المكرر
جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 76 من 87.
يُعد نمط المكرر (Iterator Pattern) نمط تصميم سلوكي يوفر طريقة للوصول إلى عناصر مجموعة ما بشكل تسلسلي دون الكشف عن بنيتها الأساسية. وسواء كنت تعمل مع مصفوفة، أو قائمة مرتبطة، أو شجرة، فإن المكرر يمنحك طريقة موحدة للتنقل عبر العناصر.
يفصل هذا النمط منطق التنقل عن المجموعة نفسها. ويتكون من مكونين رئيسيين: واجهة Iterator التي تحدد دوالاً للتنقل عبر العناصر، و Iterable (أو Aggregate) الذي ينشئ مكررات لمجموعته:
interface Iterator<T> {
boolean hasNext();
T next();
}
interface Container<T> {
Iterator<T> createIterator();
}
class BookShelf implements Container<String> {
private String[] books;
private int count = 0;
public BookShelf(int size) {
books = new String[size];
}
public void addBook(String book) {
books[count++] = book;
}
public Iterator<String> createIterator() {
return new BookIterator();
}
private class BookIterator implements Iterator<String> {
private int index = 0;
public boolean hasNext() {
return index < count;
}
public String next() {
return books[index++];
}
}
}يحافظ المكرر (iterator) على حالة التنقل الخاصة به، مما يسمح لعدة مكررات بالتنقل عبر نفس المجموعة بشكل مستقل. يستخدم العملاء المكرر دون معرفة كيفية تخزين المجموعة لبياناتها:
BookShelf shelf = new BookShelf(3);
shelf.addBook("Design Patterns");
shelf.addBook("Clean Code");
Iterator<String> iterator = shelf.createIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}تتبع واجهات Iterable و Iterator المدمجة في Java هذا النمط تماماً، ولهذا السبب يمكنك استخدام حلقات for المحسنة مع أي فئة تقوم بتنفيذ Iterable. يعد نمط المكرر (Iterator Pattern) ضرورياً عندما تحتاج إلى توفير طرق تنقل متعددة أو إخفاء الهياكل الداخلية المعقدة عن كود العميل.
التحدي
سهللنقم ببناء نظام قائمة تشغيل (playlist) باستخدام نمط المكرر (Iterator Pattern)! ستقوم بإنشاء قائمة تشغيل موسيقى تخزن الأغاني وتوفر مكررًا مخصصًا للتنقل عبرها بالتتابع — دون الكشف عن كيفية تخزين الأغاني داخليًا. هذه حالة استخدام مثالية لنمط Iterator، مما يتيح للمستخدمين التنقل في مجموعتك من خلال واجهة نظيفة وموحدة.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
Iterator.java: قم بتعريف واجهةIterator<T>العامة مع طريقتين:hasNext()التي تعيد قيمة منطقية (boolean) تشير إلى ما إذا كانت هناك عناصر أخرى موجودة، وnext()التي تعيد العنصر التالي من النوع T.Playlist.java: أنشئ الفئة المجمعة (aggregate class) التي تحمل أغانيك. يجب أن تخزنPlaylistالأغاني في مصفوفة String بسعة ثابتة (يتم تمريرها إلى المنشئ) وتتبع عدد الأغاني التي تمت إضافتها. قم بتضمين طريقةaddSong(String song)لإضافة الأغاني إلى قائمة التشغيل.تحتاج Playlist الخاصة بك إلى طريقة
createIterator()تعيدIterator<String>. قم بتنفيذ ذلك عن طريق إنشاء فئة داخلية خاصة تسمىPlaylistIteratorتقوم بتنفيذ واجهة Iterator الخاصة بك. يحافظ هذا المكرر الداخلي على موضع الفهرس (index) الخاص به ويتنقل عبر مصفوفة الأغاني، ويعيد كل أغنية بالترتيب.Song.java: أنشئ فئةSongبسيطة تغلف عنوان الأغنية. يجب أن تحتوي على منشئ (constructor) يأخذ العنوان (String)، وطريقةgetTitle()، وطريقةtoString()تعيد"Playing: " + title.Main.java: اجمع نظام المكرر الخاص بك معًا! ستتلقى مدخلاً واحدًا: قائمة بعناوين الأغاني مفصولة بفاصلة (على سبيل المثال:Bohemian Rhapsody,Stairway to Heaven,Hotel California).أنشئ Playlist بسعة 10 أغانٍ. قم بتحليل المدخلات وأضف كل عنوان أغنية إلى قائمة التشغيل. ثم احصل على مكرر من قائمة التشغيل واستخدمه للتنقل عبر جميع الأغاني، وطباعة كل واحدة مغلفة في كائن Song (والذي سيظهر كـ
Playing: [title]).بعد التكرار عبر جميع الأغاني، اطبع
Playlist complete!في سطر جديد.
ستتلقى مدخلاً واحدًا: سلسلة نصية من عناوين الأغاني مفصولة بفاصلة.
على سبيل المثال، مع المدخلات Yesterday,Imagine,Let It Be، سيكون مخرجك:
Playing: Yesterday
Playing: Imagine
Playing: Let It Be
Playlist complete!لاحظ كيف تستخدم فئة Main الخاصة بك طريقتي hasNext() و next() للمكرر للتنقل في قائمة التشغيل دون معرفة أي شيء عن بنية المصفوفة الأساسية. يقوم المكرر بتغليف كل منطق التنقل، مما يحافظ على التمثيل الداخلي للمجموعة مخفيًا عن كود العميل!
ورقة مرجعية
يُعد نمط المكرر (Iterator Pattern) نمط تصميم سلوكي يوفر طريقة للوصول إلى عناصر مجموعة ما بشكل تسلسلي دون الكشف عن بنيتها الأساسية.
يفصل هذا النمط منطق التنقل عن المجموعة نفسها باستخدام مكونين رئيسيين:
- واجهة Iterator: تحدد طرق التنقل عبر العناصر
- Iterable/Container: ينشئ مكررات (iterators) لمجموعته
البنية الأساسية
interface Iterator<T> {
boolean hasNext();
T next();
}
interface Container<T> {
Iterator<T> createIterator();
}مثال على التنفيذ
class BookShelf implements Container<String> {
private String[] books;
private int count = 0;
public BookShelf(int size) {
books = new String[size];
}
public void addBook(String book) {
books[count++] = book;
}
public Iterator<String> createIterator() {
return new BookIterator();
}
private class BookIterator implements Iterator<String> {
private int index = 0;
public boolean hasNext() {
return index < count;
}
public String next() {
return books[index++];
}
}
}الاستخدام
BookShelf shelf = new BookShelf(3);
shelf.addBook("Design Patterns");
shelf.addBook("Clean Code");
Iterator<String> iterator = shelf.createIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}يحتفظ المكرر بحالة التنقل الخاصة به، مما يسمح لعدة مكررات بالتنقل في نفس المجموعة بشكل مستقل. تتبع واجهات Iterable و Iterator المدمجة في Java هذا النمط، مما يتيح استخدام حلقات for المحسنة (enhanced for-loops).
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
// TODO: قم بإنشاء Playlist بسعة 10 أغاني
// TODO: قم بتحليل المدخلات (المفصولة بفاصلة) وأضف عنوان كل أغنية إلى playlist
// TODO: احصل على iterator من playlist باستخدام createIterator()
// TODO: استخدم iterator للتنقل عبر جميع الأغاني
// لكل أغنية، قم بتغليفها في كائن Song وقم بطباعته
// TODO: اطبع "Playlist complete!" بعد المرور على جميع الأغاني
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر