450 likes | 680 Views
תרגול 11. המשך תכנות מונחה עצמים. היום בתרגול. פולימורפיזם כללי הרשאות בהורשה מחלקות אבסטרקטיות. פולימורפיזם (רב צורתיות). התייחסות לעצמים שונים (instance) כדברים דומים (reference) בעלי מכנה משותף.
E N D
תרגול 11 המשך תכנות מונחה עצמים
היום בתרגול • פולימורפיזם • כללי הרשאות בהורשה • מחלקות אבסטרקטיות
פולימורפיזם (רב צורתיות) • התייחסות לעצמים שונים(instance)כדברים דומים(reference) בעלי מכנה משותף. • אם נרצה למשל להחזיק מערך של ספרים, אשר יוכל להחזיק ספרים מסוגים שונים, נוכל להגדיר מערך של מצביעים (reference) מטיפוס Book. • משתנה (מצביע) מטיפוס מסוים יכול להצביע על כל אובייקט שטיפוסו הוא צאצא של טיפוס זה. • לכן, מערך של מצביעים מטיפוס Book יוכל להצביע על אובייקטים מטיפוסים שיורשים את Book.
publicclass Book { private String name; privateintpages; public Book(String name, int p){ this.name = new String(name); this.pages = p; } public String getName(){ returnthis.name; } publicvoid message() { System.out.println(this.name +": " + this.pages + " pages"); } }
/* A Dictionary is a kind of Book with definitions */ publicclass Dictionary extends Book { privateintdefinitions; public Dictionary(String name, int pages, intdefs) { super(name, pages); this.definitions = defs; } publicvoid message() { System.out.println(getName() + ": " + definitions + " definitions"); } publicintgetDefinitions(){ returnthis.definitions; } }
/* A Cook Book is a kind of Book with recipes */ publicclass Cookbook extends Book { privateintrecipes; public Cookbook(String name, int pages, int recipes) { super(name, pages); this.recipes = recipes ; } publicvoid message() { System.out.println(getName() + ": " + this.recipes + " recipes "); } }
שימוש בפולימורפיזם • התייחסות לספרים שונים באופן אחיד לפי יכולות מחלקת האב: publicstaticvoid main(String[] args) { Book [] books = new Book[3]; // new above is for the array not Book objects books[0] = new Dictionary("Webster“, 690, 45056); books[1] = new Cookbook("Naked Chef“, 100, 50); books[2] = new Book("War and Peace“, 1000); for (inti=0; i<books.length; i=i+1) books[i].message(); } books Output Webster: 45056 definitions Naked Chef: 50 recipes War and Peace: 1000 pages
שימוש בפולימורפיזם • התייחסות לספרים שונים באופן אחיד לפי יכולות מחלקת האב: publicstaticvoid main(String[] args) { Book [] books = new Book[3]; // new above is for the array not Book objects books[0] = new Dictionary("Webster“, 690, 45056); books[1] = new Cookbook("Naked Chef“, 100, 50); books[2] = new Book("War and Peace“, 1000); books[0].getDefinitions(); // what would this line do ? } books // compilation error - no getDefinitions() for Book בעזרת Casting איך ניתן בכל זאת להפעיל שיטה ספציפית? ((Dictionary)books[0]).getDefinitions(); שימו לב: תכנון נכון של המחלקות יכלול דריסת פונקציותבמקום Casting
פולימורפיזם הינו "חד-כיווני" • משתנה מטיפוס Cookbook לא יכול להכיל אובייקט מטיפוס Book. (כי טיפוס מחלקת האב הוא כללי יותר) השורה: Cookbook deserts = newBook() תיתן שגיאת קומפילציה
כללי הרשאות (visibility modifiers) בהורשה public– שדות ושיטות המוגדרים כ-public ניתנים לגישה מתוך ומחוץ למחלקה. protected– שדות ושיטות המוגדרים כ-protected ניתן לגישה מתוך המחלקה וממחלקות היורשות מהמחלקה אך אינם ניתן לגישה ממחלקות אחרות *. (* ממחלקות אחרות הנמצאות ב package אחר. protected מתנהג כמו public באותו package) private – שדות ושיטות המוגדרים כ-private אינם ניתנים לגישה מחוץ למחלקה. ניסיון לגשת לשדה או שיטה כזו מחוץ למחלקה יעורר שגיאת קומפילציה.
כללי הרשאות (visibility modifiers) בהורשה • שיטות לא יכולות להידרס ע"י שיטה מרמת שיתוף נמוכה יותר, זו שגיאת קומפילציה. כלומר: • לא ניתן לדרוס שיטה public עם protected או private • לא ניתן לדרוס שיטה protected עם שיטה private class Book { public String getName() { … } } class Dictionary extends Book { protected String getName() { … } } Compilation Error שאלה: מה ההיגיון מאחורי שגיאה זו? Book b = new Dictionary(); S.O.P(b.getName());
כללי הרשאות (visibility modifiers) בהורשה • משתנים אינם עוברים דריסה בהורשה. • שיטות שאינן private עוברות בהורשה. • נועדו למימוש פנימי של מחלקת האב, ואינם ניתנים לשימוש ע"י תת מחלקה. • תת-המחלקה לא מכירה את המשתנים והשיטות הפרטיות של המחלקת האב, ממנה היא יורשת, ולכן עשויה גם להכריז על משתנים ושיטות פרטיות בעלי אותו שם וחתימה. • דוגמה: • חנות היא סוג של עסק • עסק: דמי שכירות, עובדים, הוצאות חודשיות • חנות: פריטי סחורה, הוצאות חודשיות הכוללות אחזקת מלאי. • כל אחד מהם מומש ע"י מתכנת אחר • בכל אחד מימוש שונה לשיטה פרטית - calcSum()
publicclass Business { protected Employee[] employees; protecteddoublemonthlyRent; ...... // calculates total monthly expenses publicdouble monthlyExpenses() { double salaries = calcSum(); returnthis.monthlyRent + salaries; } // calculates monthly salaries privatedouble calcSum() { double sum = 0; for (int i=0; i<this.employees.length; i=i+1) sum = sum + this.employees[i].getSalary(); return sum; } ...... } קריאה לשיטה פרטית
public class Shop extends Business { protected Item[] items; ...... // override: calculates total monthly expenses publicdouble monthlyExpenses() { double itemPrices = calcSum(); return itemPrices + super.monthlyExpenses(); } // No override: calculates total item prices privatedouble calcSum() { double sum=0; for (int i=0; i<this.items.length; i=i+1){ sum = sum + this.items[i].getPrice(); } return sum; } ...... } קריאה לשיטה פרטית שאלה: אם נשנה את המאפיין של calcSum ל-protected מה תחשב monthlyExpenses?
סיכום חוקי גישה לשיטות ושדות בהורשה ב Java: • טיפוס המשתנה (טיפוס המצביע/ reference type) קובע בזמן הידור אילו שיטות ניתן להפעיל על המשתנה ולאילו שדות של המשתנה ניתן לגשת. • בקריאה לשיטה שאינה פרטית המופיעה במחלקת-אב ובתת-מחלקה, אם ע"י קריאה ישירה או ע"י שימוש באופרטור השייכות (.), הקוד המופעל נקבע בהתאם למחלקת האובייקט בפועלinstance type) ) שעליו מופעלת השיטה (ומשם בסריקה מלמטה למעלה לפי היררכית ההורשה). • שיטה פרטית נגישה רק בתוך ה- scope של המחלקה בה היא מוגדרת. בקריאה לשיטה פרטית, הקוד המופעל נקבע לפי ה -scope בו ממוקמת הקריאה. • בגישה לשדה (קריאה או השמה), ע"י גישה ישירה (ללא אופרטור השייכות), הגישה לשדה נקבעת לפי ה-scope בה ממוקמת הגישה (ומשם בסריקה מלמטה למעלה לפי היררכית ההורשה). • בגישה לשדה (קריאה או השמה) שאינו פרטי, ע"י אופרטור השייכות, בהתאם למחלקת טיפוס המשתנהreference type) ) שעליו מופעלת השיטה (ושם בסריקה מלמטה למעלה לפי היררכית ההורשה).
דוגמה להורשה (שאלה ממבחן)
publicclass A { privateintx; publicinty; public A(int x) { this.x = x; this.y = 2*x; } publicintgetX() { returnx; } publicintdoubleX() { return 2 * getX(); } publicinttripleX() { return 3 * x; } privateintsubXhelper() { returnx - 1; } publicintsubX() { returnsubXhelper(); } } publicclass B extends A { privateintx; publicinty; public B(intxA, intxB) { super(xA); this.x = xB; this.y = xA + xB; } publicintgetX() { returnx; } publicintsuperX() { returnsuper.getX(); } publicinttenTimesX() { return 10*x; } privateintsubXhelper() { returnx-2; } } A a = new A (1); A b = new B (2, 22); Output / Notes System.out.println(a.getX()); System.out.println(b.getX()); System.out.println(b.superX()); 1 22 Compilation Error !! if (b instanceof B) System.out.println(b.superX()); Compilation Error !!
publicclass A { privateintx; publicinty; public A(int x) { this.x = x; this.y = 2*x; } publicintgetX() { returnx; } publicintdoubleX() { return 2 * getX(); } publicinttripleX() { return 3 * x; } privateintsubXhelper() { returnx - 1; } publicintsubX() { returnsubXhelper(); } } publicclass B extends A { privateintx; publicinty; public B(intxA, intxB) { super(xA); this.x = xB; this.y = xA + xB; } publicintgetX() { returnx; } publicintsuperX() { returnsuper.getX(); } publicinttenTimesX() { return 10*x; } privateintsubXhelper() { returnx-2; } } A a = new A (1); A b = new B (2, 22); Output / Notes B bb = (B)b; System.out.println(bb.superX()); 2 System.out.println(((B)b).superX()); 2 System.out.println(a.tripleX()); System.out.println(b.tripleX()); 3 6
publicclass A { privateintx; publicinty; public A(int x) { this.x = x; this.y = 2*x; } publicintgetX() { returnx; } publicintdoubleX() { return 2 * getX(); } publicinttripleX() { return 3 * x; } privateintsubXhelper() { returnx - 1; } publicintsubX() { returnsubXhelper(); } } publicclass B extends A { privateintx; publicinty; public B(intxA, intxB) { super(xA); this.x = xB; this.y = xA + xB; } publicintgetX() { returnx; } publicintsuperX() { returnsuper.getX(); } publicinttenTimesX() { return 10*x; } privateintsubXhelper() { returnx-2; } } A a = new A (1); A b = new B (2, 22); Output / Notes Run-time Error: ClassCastException: A cannot be cast to B System.out.println(((B)a).tenTimesX()); System.out.println(((B)b).tenTimesX()); System.out.println(b.doubleX()); 220 44 System.out.println(b.subX()); 1
publicclass A { privateintx; publicinty; public A(int x) { this.x = x; this.y = 2*x; } publicintgetX() { returnx; } publicintdoubleX() { return 2 * getX(); } publicinttripleX() { return 3 * x; } privateintsubXhelper() { returnx - 1; } publicintsubX() { returnsubXhelper(); } } publicclass B extends A { privateintx; publicinty; public B(intxA, intxB) { super(xA); this.x = xB; this.y = xA + xB; } publicintgetX() { returnx; } publicintsuperX() { returnsuper.getX(); } publicinttenTimesX() { return 10*x; } privateintsubXhelper() { returnx-2; } } A a = new A (1); A b = new B (2, 22); Output / Notes System.out.println(a.y); System.out.println(b.y); System.out.println(((B)b).y); B bb= (B)b; System.out.println(bb.y); System.out.println(((A)bb).y); 2 4 24 24 4
מחלקות אבסטרקטיות • כאשר רוצים לחלוק קוד משותף בין מספר מחלקות למרות שאין רצון לאפשר יצירת אובייקטים ממחלקת האב. • מכילה קוד משותף. • קובעת אילו שיטות אבסטרקטית על תתי המחלקות לממש. • תת-מחלקה קונקרטית מממשת שיטות אבסטרקטיות.
מחלקות אבסטרקטיות public abstractclass<name> { public abstractvoid <method name> ( ... ); … } • במחלקה אבסטרקטית יכולות להיות שיטות רגילות, כמו בכל מחלקה. • בנוסף יכולות להיות לה שיטות אבסטרקטיות: שיטות שההגדרה שלהן קיימת אבל אין להן מימוש. • אנו מכריזים על מחלקה או על שיטה כאבסטרקטית בעזרת המילה השמורה abstract.
מחלקות אבסטרקטיות • מחלקה אבסטרקטית -לא ניתן ליצור ממנה מופעים. publicabstractclass Game { public Game() { … } … } Game g = new Game(); // Compilation error! • מחלקה שמרחיבה מחלקה אבסטרקטית ולא מממשת את כל השיטות האבסטרקטיות, חייבת להיות אבסטרקטית בעצמה.
Spy Robot • Spy Robot (רובוט מעקב) הינו רובוט הנשלט מרחוק ומאפשר צילום תמונות ושלחתם. • רובוט מעקב יכול לבצע את הפעולות הבאות: • לצלם תמונות ולשדר אותן • לזוז קדימה / אחרונה • להסתובב ימינה / שמאל
Spy Robot • נסתכל על 2 רובוטי מעקב • שניהם יכולים לצלם תמונות ולשדר באותה דרך אך הם זזים בדרכים שונות.
Spy Robot publicabstractclass SpyRobot { private String model; public SpyRobot(String model) { this.model=model; } public String getModel() { returnthis.model; } publicabstractvoid moveForward(); publicabstractvoid moveBackward(); publicabstractvoid turnLeft(); publicabstractvoid turnRight(); publicvoid takePicture() { ... } publicvoid chargeBattery() { ... } }
Roboquad – Spy Robot publicclass LegsSpyRobot extends SpyRobot { public LegsSpyRobot() { super("Roboquad"); } publicvoid moveForward() { for(int i=0; i<4; i++) this.moveLeg(i, 1); } publicvoid moveBackward() { for(int i=0; i<4; i++) this.moveLeg(i, -1); } publicvoid turnLeft() { this.moveLeg(0,-1); this.moveLeg(1,-1); this.moveLeg(2,1); this.moveLeg(3,1); } // direction {1=forward, -1=backward} privatevoid moveLeg(int legId, int dir) { ... }; } 3 1 2 0 publicvoid turnRight() { this.moveLeg(0,1); this.moveLeg(1,1); this.moveLeg(2,-1); this.moveLeg(3,-1); }
Spyke – Spy Robot publicclass WheelsSpyRobot extends SpyRobot { public WheelsSpyRobot() { super("Spyke"); } publicvoid moveForward() { this.turnWheels(1,1); } publicvoid moveBackward() { this.turnWheels(-1,-1); } publicvoid turnLeft() { this.turnWheels(0,-1); } publicvoid turnRight() { this.turnWheels(-1,0); } // direction {1=forward, 0=stop, -1=backward} privatevoid turnWheels(int rightDir,int leftDir) { ... }; // move features publicvoid waveHands() { ... } }
Fly – Spy Robot את זה אתם כבר יכולים לממש לבד ?
דוגמא נוספת • נממש משחקים מתורת המשחקים ע"י הורשה: • משחק מוגדר על ידי מערכת של פעולות אפשריות ושיטת ניקוד. • במשחק משחקים שני שחקנים כאשר שני השחקנים בוחרים פעולה בו-זמנית. • בהינתן שתי הבחירות של השחקנים יקבלו השחקנים ניקוד ע"פ בחירתם.
דוגמה: אבן נייר ומספריים • בחירה מבין שלוש הפעולות האפשריות (אבן, נייר או מספריים). • אבן שוברת מספריים. • מספריים גוזרים נייר. • נייר עוטף אבן.
דוגמה: דילמת האסיר • המשטרה עוצרת שני עבריינים שביצעו פשע משותף, ומפרידה ביניהם לצורך חקירה. • המשטרה מציעה לכל אחד מהם להעיד נגד רעהו, וכפרס מובטח לעד עונש מופחת. • בחירה מבין הפעולות האפשריות: להעיד או לשתוק. • ניקוד: • אם שניהם יעידו, ייכנס כל אחד מהם לכלא לחמש שנים. • אם רק אחד מהם יעיד ורעהו ישתוק, העד יצא מיד לחופשי וחברו ייכלא ל-15 שנה. • אם שניהם ישתקו, יכנס כל אחד מהם לשנה בכלא.
המחלקות שעלינו לממש • פעולה Action • שם הפעולה ("אבן") • שחקן Player • שם השחקן (" Andrea Farina ") • מספר נקודות • בחירת פעולה(מהלך) מתוך קבוצת פעולות אפשריות. • משחק Game • קבוצת פעולות אפשריות • שיטת ניקוד • 2 שחקנים • שם המשחק
מימוש המחלקה של פעולה כללית publicclass Action { private String name; public Action(String n) { this.name = n; } public String getName(){ returnthis.name; } publicboolean equals(Object other) { boolean ans = false; if(other instanceof Action) ans = this.name.equals(((Action)other).name); return ans; } }
מימוש המחלקה של שחקן כללי publicabstractclass Player { private String name; privateintscore; public Player(String pname){ this.name= pname; this.score =0; } publicabstract Action selectAction(Action[] actions); publicboolean isWinner(Player p){ return (this.score > p.getScore()); } publicvoid updateScore(int score){ this.score = this.score + score; } publicint getScore(){ returnthis.score; } }
מימוש שחקן אקראי publicclass RandomPlayer extends Player{ public RandomPlayer(String name) { super(name); } public Action selectAction(Action[] actions){ int randIdx = (int)(Math.random()*actions.length); return actions[randIdx]; } }
מימוש שחקן עיקבי publicclass ConsecutivePlayer extends Player { privateintlastIdx; public ConsecutivePlayer(String name) { super(name); this.lastIdx = 0; } public Action selectAction(Action[] actions) { this.lastIdx = (this.lastIdx + 1) % actions.length; return actions[this.lastIdx]; } }
מימוש משחק כללי publicabstractclass Game { private Player p1, p2; private String name; //game name protected Action[] actions; // the set of actions public Game(Player p1, Player p2, String name){ this.p1 = p1; this.p2 = p2; this.name = name; this.initActions(); } // There is no real list of actions in a general game protectedabstractvoid initActions(); …
מימוש משחק כללי (המשך) publicabstractclass Game { … publicvoid play(intturnCount) { for (inti=0; i<turnCount; i=i+1) this.playSingleTurn(); } privatevoidplaySingleTurn() { /* the selection order is not important * as each player does not * know the choice of the other player */ Action a1 = this.p1.selectAction(actions); Action a2 = this.p2.selectAction(actions); this.rewardPlayers(a1, a2); } // There is no real scoring strategy in a general game protectedabstractvoidrewardPlayers(Action a1, Action a2); …
מימוש משחק כללי (המשך) publicabstractclass Game { … public Player getWinner () { if (this.p1.isWinner(this.p2)) returnthis.p1; else returnthis.p2; } protected Player getFirstPlayer() { returnthis.p1; } protected Player getSecondPlayer() { returnthis.p2; } }
מימוש המשחק אבן נייר ומספריים publicclass RockPaperScissors extends Game{ public RockPaperScissors(Player p1, Player p2) { super(p1, p2, "Rock Paper Scissors"); } protectedvoid initActions(){ this.actions = new Action[3]; this.actions[0] = new Action("rock"); this.actions[1] = new Action("paper"); this.actions[2] = new Action("scissors"); } ...
protectedvoid rewardPlayers(Action a1, Action a2) { int p1score = 0; if (!(a1.getName().equals(a2.getName()))) { if ((a1.getName().equals("rock") && a2.getName().equals("scissors")) || (a1.getName().equals("paper") && a2.getName().equals("rock")) || (a1.getName().equals("scissors") && a2.getName().equals("paper"))) { p1score = 1; } else { p1score = -1; } } this.getFirstPlayer().updateScore(p1score); this.getSecondPlayer().updateScore(-p1score); } }
מימוש המשחק דילמת האסיר publicclass PrisonerDilemmas extends Game{ public PrisonerDilemmas(Player p1, Player p2) { super(p1, p2, "Prisoner's Dilemma"); } protectedvoid initActions(){ this.actions = new Action[2]; this.actions[0] = new Action("silent"); this.actions[1] = new Action("blame"); } ...
protectedvoid rewardPlayers(Action a1, Action a2) { if (a1.getName().equals("blame") && a2.getName().equals("blame")) { this.getFirstPlayer().updateScore(-5); this.getSecondPlayer().updateScore(-5); } if (a1.getName().equals("silent") && a2.getName().equals("blame")) { this.getFirstPlayer().updateScore(-15); this.getSecondPlayer().updateScore(0); } if (a1.getName().equals("blame") && a2.getName().equals("silent")) { this.getFirstPlayer().updateScore(0); this.getSecondPlayer().updateScore(-15); } if (a1.getName().equals("silent") && a2.getName().equals("silent")) { this.getFirstPlayer().updateScore(-1); this.getSecondPlayer().updateScore(-1); } } }