תבנית האיטרטור
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Java של Coddy. שיעור 76 מתוך 87.
תבנית האיטרטור היא תבנית עיצוב התנהגותית שמספקת דרך לגשת ברצף לאיברים באוסף, בלי לחשוף את המבנה הפנימי שלו. בין אם עובדים עם מערך, רשימה מקושרת או עץ, האיטרטור מאפשר לעבור על האיברים באופן אחיד.
התבנית מפרידה את לוגיקת המעבר מהאוסף עצמו. היא כוללת שני רכיבים עיקריים: ממשק 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++];
}
}
}האיטרטור שומר את מצב המעבר שלו, וכך מאפשר למספר איטרטורים לעבור על אותו אוסף באופן עצמאי. לקוחות משתמשים באיטרטור בלי לדעת כיצד האוסף מאחסן את הנתונים שלו:
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());
}הממשקים המובנים של Java, Iterable ו־Iterator, פועלים לפי הדפוס המדויק הזה, ולכן אפשר להשתמש בלולאות for מורחבות עם כל מחלקה שמממשת את Iterable. דפוס האיטרטור חיוני כשצריך לספק כמה שיטות למעבר על אוסף או להסתיר מבנה פנימי מורכב מקוד הלקוח.
אתגר
קלבואו נבנה מערכת של רשימת השמעה באמצעות תבנית Iterator! תיצור רשימת השמעה של מוזיקה שמאחסנת שירים ומספקת איטרטור מותאם אישית כדי לעבור עליהם ברצף, בלי לחשוף את אופן האחסון הפנימי של השירים. זהו מקרה שימוש מושלם בתבנית Iterator, שמאפשרת למשתמשים לנווט באוסף שלך באמצעות ממשק אחיד ופשוט.
תארגן את הקוד שלך בארבעה קבצים:
Iterator.java: הגדר את הממשק הגנרי שלךIterator<T>עם שתי מתודות:hasNext()שמחזירה ערך בוליאני המציין אם קיימים איברים נוספים, ו-next()שמחזירה את האיבר הבא מסוג T.Playlist.java: צור את מחלקת האוסף שמחזיקה את השירים שלך. המחלקהPlaylistשלך צריכה לאחסן שירים במערך String בעל קיבולת קבועה (שנמסרת לבנאי) ולעקוב אחר מספר השירים שנוספו. הוסף מתודהaddSong(String song)להוספת שירים לרשימת ההשמעה.מחלקת Playlist שלך צריכה לכלול מתודה
createIterator()שמחזירהIterator<String>. ממש את המתודה באמצעות יצירת מחלקה פנימית פרטית בשםPlaylistIteratorשמממשת את ממשק Iterator שלך. האיטרטור הפנימי הזה מנהל מיקום אינדקס משלו ועובר על מערך השירים, תוך החזרת כל שיר לפי הסדר.Song.java: צור מחלקתSongפשוטה שעוטפת כותרת של שיר. עליה לכלול בנאי שמקבל את הכותרת (String), מתודהgetTitle()ומתודהtoString()שמחזירהPlaying: [title].Main.java: חבר את מערכת האיטרטור שלך! תקבל קלט אחד: רשימה של כותרות שירים המופרדות בפסיקים (לדוגמה:Bohemian Rhapsody,Stairway to Heaven,Hotel California).צור רשימת השמעה עם קיבולת של 10 שירים. נתח את הקלט והוסף כל כותרת של שיר לרשימת ההשמעה. לאחר מכן קבל איטרטור מרשימת ההשמעה והשתמש בו כדי לעבור על כל השירים, ולהדפיס כל אחד מהם כשהוא עטוף באובייקט Song (שיוצג כך:
Playing: [title]).לאחר המעבר על כל השירים, הדפס
Playlist complete!בשורה חדשה.
תקבל קלט אחד: מחרוזת עם כותרות שירים המופרדות בפסיקים.
לדוגמה, עבור הקלט Yesterday,Imagine,Let It Be, הפלט שלך יהיה:
Playing: Yesterday
Playing: Imagine
Playing: Let It Be
Playlist complete!שים לב איך מחלקת Main שלך משתמשת במתודות hasNext() ו-next() של האיטרטור כדי לעבור על רשימת ההשמעה, מבלי לדעת דבר על המבנה של המערך הבסיסי. האיטרטור עוטף את כל לוגיקת המעבר, וכך משאיר את הייצוג הפנימי של האוסף מוסתר מקוד הלקוח!
נסו בעצמכם
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: פענח את הקלט (מופרד בפסיקים) והוסף כל שם של שיר לרשימת ההשמעה
// TODO: קבל איטרטור מרשימת ההשמעה באמצעות createIterator()
// TODO: השתמש באיטרטור כדי לעבור על כל השירים
// עבור כל שיר, עטוף אותו באובייקט Song והדפס אותו
// TODO: הדפס "Playlist complete!" לאחר מעבר על כל השירים
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים
קבצים חיצונייםמבוא לתכנות מונחה עצמיםמחלקות לעומת אובייקטיםמילת המפתח thisמתודותשדות (מאפיינים)מתודת הבנאיהעמסת בנאיםחזרה – מחשבון פשוט4ירושה
ירושה בסיסית (extends)מילת המפתח superדריסת מתודות (@Override)שרשור בנאיםהמחלקה Objectירושה יחידה ורב־רמתיתלמה אין ירושה מרובה של מחלקותסיכום – היררכיית עובדים7מתודות מיוחדות ומחלקת Object
מתודת toString()equals() ו-hashCode()מתודת clone()compareTo() ו-Comparableהממשק Comparatorסיכום – מיון מותאם אישית2מצייני גישה ואנקפסולציה
סקירה של רמות הגישהמתודות Getter ו-Setterהסתרת מידעמילת המפתח finalסיכום – מנהל חשבון בנק5פולימורפיזם
יסודות העמסת מתודותדריסת מתודות (בזמן ריצה)המרה כלפי מעלה וכלפי מטההאופרטור instanceofמחלקות ומתודות מופשטותסיכום – מחשבון צורות8מושגי OOP מתקדמים
הרכבה מול ירושהאגרגציה מול הרכבהמחלקות פנימיות, מקוננות ואנונימיותEnums ומתודות של EnumRecords (Java 16+)מחלקות חתומות (Java 17+)3מאפייני מחלקה וחברים סטטיים
משתני מופע לעומת משתנים סטטייםמתודות סטטיותבלוקים סטטייםקבועים (static final)סיכום — מונה ומחלקת עזר6ממשקים ומחלקות מופשטות
מבוא לממשקיםמימוש ממשקיםמימוש של כמה ממשקיםמתודות ברירת מחדל וסטטיות בממשקמחלקות מופשטות לעומת ממשקיםממשקים פונקציונלייםסיכום – מערכת תשלומים9גנריות
מבוא לגנריותמחלקות גנריותשיטות גנריותפרמטרי טיפוס מוגבליםתווים כלליים (?, extends, super)סיכום – מכל גנרי12תבניות עיצוב — חלק 2
תבנית הפקודהתבנית המתאםתבנית העיצובתבנית המתודה התבניתיתתבנית המצבתבנית המרוכבתתבנית האיטרטורתרגלו בעצמכם: קומפיילר Java אונליין