שאלת מבחן בתכנות מונחה עצמים - אוניברסיטת בר-אילן 2014 - מחלקות

משחק ה-Arkanoid שפיתחנו במהלך הסמסטר תפס את תשומת ליבה של חברת פרסום גדולה, והם רוצים להשתמש בו כחלק מפתרונות הפרסום שלהם. ברצונם ליצור גרסה מותאמת של המשחק לכל אחד מהלקוחות: סידור בלוקים, ציורי בלוקים וציורי מסכים המתאימים למוצרי החברה. ברצונם לבצע את ההתאמות האלו על ידי המעצבים הגרפיים של החברה, ללא צורך בגישה לקוד של המשחק. למרבה השמחה יכולות אלו כבר נתמכות במשחק כפי שהוא קיים כיום. עם זאת, כדי לסגור את העסקה, הם מעוניינים ביכולת נוספת. הם היו רוצים להגדיר עבור חלק מהבלוקים את ההתנהגות הבאה: עבור חלק מהבלוקים, כל פגיעה בהם תגרור "באנר" שמכיל תמונה קבועה מראש שיזוז על המסך מצד שמאל לצד ימין במהירות מסוימת ובגובה מסוים. לכל בלוק ניתן יהיה להגדיר האם הוא גורר באנר או לא, ואם כן באיזו תמונה, איזו מהירות ואיזה גובה הוא יוצג. כמובן שגם ההגדרות האלו (איזה באנר, איזו מהירות, איזה גובה ואילו בלוקים יגררו את ההתנהגות הזו) יקבעו על ידי חברת הפרסום ללא גישה לקוד ה-Java של המשחק.

תארו את כל השינויים וההתאמות הנדרשים במשחק להוספת היכולת הזו. אין צורך לכתוב קוד, אך יש לכתוב תיאור מפורט של השינויים, כאילו הייתם מסבירים לגורם שלישי כיצד לכתוב את הקוד.


שימו לב: יש לשמור על עקרונות התכנות מונחה העצמים, והפרדת תחומי האחריות של מחלקות שונות. כמו כן, אסור לשנות את המימושים של המחלקה Block או מחלקות שמרחיבות או יורשות ממנה. בפרט, הבלוק לא אמור לדעת שהוא גורם לבאנר להופיע. ניתן לעבור על חוקים אלו, בעלות של חצי מהניקוד עבור השאלה.
העתק שאלה
שתף שאלה
סמן כחשוב
סמן כבוצע
אוניברסיטת בר-אילןמועד ג2014סמסטר ב
מחלקותאובייקטיםממשקיםפולימורפיזםהסתרת מידעתבניות עיצוב
השתמשו במנגנון ה-HitListener הקיים כדי להגיב לאירוע פגיעה בבלוק, מבלי לשנות את מחלקת ה-Block עצמה. יש ליצור HitListener חדש שיהיה אחראי על יצירת הבאנר.
כדי להוסיף את היכולת המבוקשת תוך שמירה על עקרונות תכנות מונחה עצמים והמגבלות שהוצגו, יש לבצע את השינויים הבאים המבוססים על תבנית העיצוב Observer ו-תבנית העיצוב Factory.

1. יצירת מחלקה חדשה `Banner`:
* יש ליצור מחלקה חדשה Banner שתייצג את הבאנר הפרסומי.

* מחלקה זו תממש את הממשק `Sprite`, כך שניתן יהיה להוסיף אותה לאוסף ה-Sprites של המשחק, והיא תצויר ותתעדכן בכל פעימה של לולאת המשחק.

* למחלקה יהיו התכונות הבאות: תמונה (Image), מיקום (Point), ומהירות (Velocity).

* הבנאי של המחלקה יקבל את נתיב התמונה, מהירות אופקית, וגובה (קואורדינטת y) קבועים. המיקום ההתחלתי יהיה בקצה השמאלי של המסך בגובה הנתון.

* המתודה timePassed תעדכן את מיקום הבאנר בהתאם למהירות שלו. יש לממש לוגיקה שתסיר את הבאנר מהמשחק (באמצעות קריאה למתודה כמו game.removeSprite(this)) כאשר הוא יוצא מגבולות המסך הימני. לשם כך, הבאנר יצטרך להחזיק רפרנס לאובייקט המשחק (GameLevel).

* המתודה drawOn תצייר את תמונת הבאנר במיקומו הנוכחי.


2. יצירת מאזין חדש `BannerCreator`:
* זהו לב הפתרון, המממש את תבנית ה-Observer. ניצור מחלקה חדשה בשם BannerCreator אשר תממש את הממשק `HitListener`.

* מחלקה זו תהיה אחראית על יצירת אובייקט Banner והוספתו למשחק כתגובה לאירוע פגיעה בבלוק.

* הבנאי שלה יקבל רפרנס לאובייקט המשחק (GameLevel) ואת המאפיינים הדרושים ליצירת הבאנר הספציפי (נתיב תמונה, מהירות וגובה).

* המתודה hitEvent(Block beingHit, Ball hitter) תכיל את הלוגיקה הבאה:

1. יצירת מופע חדש של Banner באמצעות המאפיינים שנשמרו במאזין.

2. הוספת ה-Banner החדש לאוסף ה-Sprites של המשחק באמצעות קריאה ל-gameLevel.addSprite(newBanner).

* בצורה זו, מחלקת Block אינה מודעת כלל לקיומם של באנרים. היא רק מבצעת את חובתה להודיע לכל המאזינים הרשומים לה על אירוע פגיעה. זהו יישום של עקרון הפרדת תחומי אחריות (Separation of Concerns).


3. קונפיגורציה חיצונית ו-Factory ליצירת מאזינים:
* כדי לאפשר הגדרה של הבאנרים ללא שינוי בקוד, ניצור קובץ הגדרות חיצוני (למשל banner_definitions.txt).

* קובץ זה ימפה בין סמל של בלוק (כפי שמופיע בקובץ הגדרת השלב) לבין מאפייני הבאנר שהוא אמור ליצור.

* לדוגמה, שורה בקובץ יכולה להיראות כך: bdef symbol:X image:ads/banner.png speed:150 y:300

* ניצור מחלקה חדשה, `BannerFactory`, שתהיה אחראית על קריאת קובץ ההגדרות וייצור של אובייקטי BannerCreator מתאימים.

* ה-Factory יקרא את הקובץ וישמור את המיפויים (למשל בתוך Map).

* הוא יספק מתודה כמו createListener(String blockSymbol, GameLevel game) אשר בודקת אם לסמל הבלוק הנתון יש הגדרת באנר. אם כן, היא תיצור ותחזיר מופע חדש של BannerCreator עם המאפיינים המתאימים; אחרת, תחזיר null.


4. שילוב בתהליך טעינת השלב:
* יש לעדכן את הקוד שאחראי על טעינת שלב מקובץ.

* בתחילת טעינת השלב, יש ליצור מופע של BannerFactory ולטעון אליו את קובץ הגדרות הבאנרים.

* בתוך הלולאה שיוצרת את הבלוקים של השלב, עבור כל בלוק שנוצר, יש לבצע את הפעולות הבאות:

1. לקרוא את הסמל של הבלוק מקובץ השלב.

2. לקרוא למתודה bannerFactory.createListener עם סמל הבלוק ורפרנס למשחק.

3. אם המתודה החזירה אובייקט HitListener (כלומר, לא null), יש לרשום אותו כמאזין של הבלוק שזה עתה נוצר באמצעות block.addHitListener(theNewListener).


באמצעות ארכיטקטורה זו, השגנו את כל המטרות: הוספנו את היכולת החדשה, ההגדרות שלה חיצוניות וניתנות לשינוי ללא הידור מחדש, ולא שינינו את מחלקת Block או את היורשות ממנה, תוך שמירה על עקרונות OOP חשובים.