Download presentation
Presentation is loading. Please wait.
1
הכמסה – Encapsulation עקרון ההכמסה
הכמסה מגדירה מה יכול משתמש חיצוני לדעת על האובייקט, ומה לא. הרעיון הוא לומר מה האובייקט יכול לעשות ולא כיצד הוא עושה זאת. כלומר חשיפת הפונקציונאל ולא המימוש. פשטות של האובייקט (אין צורך להתגאות ב"איך" מימשנו) . מאותה סיבה נכמיס שיטות עזר (למשל שיטות עזר רקורסיביות). רעיון זה מאפשר לנו לשלוט על השמת ערכים לשדות המחלקה.
2
המחלקה Object
3
נסתכל בגרסה פשוטה של המחלקה Point: public class Point{ private double x; private double y; public Point(double x, double y){ this.x = x; this.y = y; } public boolean equals(Point other) { return (x == other.x) && (y == other.y); public double getX(){return x;} public double getY(){return y;} } //class Point x y getX() getY() כמו שהגדרנו שדות בCircle- בהרשאת private, כר נעשה גם ב .Point. נשתמש בשיטת הgetter getX() לקרא את השדה x
4
קבוצה של נקודות x y getX() getY() x y getX() getY() x y getX() getY() arr x y getX() getY() Point[] arr = new Point[7]; arr[0] = new Point(1,2); כאשר אנו רוצים להוסיף או למחוק נקודה, כל מה שנצטרך לעשות זה לשנות את המערך. היכן נשמור את אוסף זה? כיצד נסתיר את מימוש הפעולות ונחשוף רק את הפעולות עצמן?
5
PointSet התנהגות public void add(Point p){…}
x y getX() getY() נדרוש שכל נקודה תופיע פעם אחת בלבד x y getX() getY() x y getX() getY() x y getX() getY() public void add(Point p){…} public void remove(Point p){…} public int size(){…} public boolean contains(Point p){…} קודם כל, נחשוב מהי הפונקצונאליות שאנו רוצים מהאוביקטים שלנו. אחר-כך, נחליט מה צריך לכלול המצב בכדי שנוכל לממש את הפונקציונאליות. 5 5
6
PointSet מצב איך נייצג את המצב של הקבוצה?
x y getX() getY() x y getX() getY() x y getX() getY() x y getX() getY() איך נייצג את המצב של הקבוצה? ניזכר כי לא כל המצבים האפשריים הם "מצבים חוקיים". כדאי לשאול את הסטודנטים: מהו מצב חוקי במקרה שלנו? כאשר נממש את המחלקה, נשתמש בהכמסה בכדי לדאוג שאובייקטים נמצאים תמיד במצבים חוקיים. למשל, לא יתכן כי קיימת נקודה המופיעה יותר מפעם אחת במערך (זה יקשה על ביצוע פעולת ההסרה), size שלילי, או שקיים תא i < size במערך שאינו מכיל מצביע לנקודה. 6 6
7
PointSet מצב נגדיר מצב חוקי: כל נקודה מופיעה פעם אחת בלבד.
x y getX() getY() x y getX() getY() x y getX() getY() x y getX() getY() נגדיר מצב חוקי: כל נקודה מופיעה פעם אחת בלבד. מספר הנקודות שווה ל- size 0<=size<=elelents.length ההפניות לנקודות מרוכזות בתאים השמאליים ביותר של המערך. קודם כל, נחשוב מהי הפונקצונאליות שאנו רוצים מהאוביקטים שלנו. אחר-כך, נחליט מה צריך לכלול המצב בכדי שנוכל לממש את הפונקציונאליות. Point[] elements int size 7 7
8
PointSet public class PointSet { ? Point[] elements; ? int size; ? PointSet(int capacity){ elements = new Point[capacity]; size = 0; }
9
PointSet public class PointSet { private Point[] elements; private int size; public PointSet(int capacity){ elements = new Point[capacity]; size = 0; }
10
PointSet בדיקת גודל הקבוצה: public int size(){ return size; } הוספת איבר: public void add(Point p){ if (p != null) { if ((!contains(p)) && (size<elements.length)){ elements[size] = p; size = size + 1; }//add שימו לב שאנו משתמשים ב-contains לפני שכתבנו אותה – מתכנת צריך להתרגל לחשוב כך.
11
PointSet הסרת איבר (נשים לב לשמירת חוקיות המצב): public void remove(Point p){ boolean found=false; for (int i=0; (!found) && (i<size); i=i+1){ if (elements[i].equals(p)){ elements[i] = elements[size-1]; elements[size-1] = null; size = size - 1; found=true; } }//remove הסרה של אבר. נרצה לשמור על סדר תקין של המערך, כלומר שהמערך מלא מההתחלה עד לsize. 11 11
12
PointSet בדיקת שייכות: public boolean contains(Point p){ // is p a member of this set? boolean found = false; for (int i=0;(!found)&&(i < size); i=i+1){ if (elements[i].equals(p)){ found = true; } return found; }//contains 12 12
13
PointSet רגע, יש כאן קוד שחוזר על עצמו! כלל חשוב בהנדסת תוכנה: השתדלו שפונקציונאליות מסוימת תיהיה כתובה במקום אחד בלבד: ? int indexOf(Point p){ for (int i=0; i<size; i=i+1){ if (elements[i].equals(p)){ return i; } return -1; } //indexOf כדאי לשאול את השאלה: "מה צריכה להיות ההרשאה של indexOf, פרטית או ציבורית?" תשובה – פרטית: אף אחד בחוץ לא אמור להכיר את העובדה שבחרנו להשתמש במערך בכדי להחזיר את הנקודות. על ידי כך שלא התחיבנו לספק את ההתנהגות indexOf, נאפשר לעצמנו גמישות, כאשר נוכל להחליף את מבנה הנתונים בעתיד. 13 13
14
PointSet private int indexOf(Point p){ for (int i=0; i<size; i=i+1){ if (elements[i].equals(p)){ return i; } return -1; } //indexOf public void remove(Point p){ int i = indexOf(p); if (i != -1){ elements[i] = elements[size-1]; elements[size-1] = null; size = size - 1; }//remove public boolean contains(Point p){ return indexOf(p) != -1; }//contains כדאי לשאול את השאלה: "מה צריכה להיות ההרשאה של indexOf, פרטית או ציבורית?" תשובה – פרטית: אף אחד בחוץ לא אמור להכיר את העובדה שבחרנו להשתמש במערך בכדי להחזיר את הנקודות. על ידי כך שלא התחיבנו לספק את ההתנהגות indexOf, נאפשר לעצמנו גמישות, כאשר נוכל להחליף את מבנה הנתונים בעתיד. 14 14
15
תכנית המבחן של המחלקה PointSet:
public static void main(String[] args){ PointSet pSet = new PointSet(100); Point p1 = new Point(1,1); Point p2 = new Point(1,2); Point p3 = new Point(1,1); pSet.add(p1); pSet.add(p2); System.out.println(pSet.contains(p3)); pSet.add(p3); pSet.remove(p1); }
16
CircleSet עתה נרצה להגדיר אוסף של מעגלים. כיצד נעשה זאת? נגדיר מחלקה CircleSet... במה שתהיה שונה המחלקה PointSet מהמחלקה CircleSet? מה אם נרצה להגדיר אוסף משחקים?? האם באמת נצטרך לכל טיפוס לשכפל את אותו הקוד ולשנות אך ורק את טיפוס המערך והפרמטרים? פתרון אפשרי: אם היינו יכולים להגדיר טיפוס כללי אשר יוכל להצביע לכל סוג של אובייקט, נוכל להגדיר מחלקה אחת כללית של קבוצה. פתרון אפשרי: אם היינו יכולים להגדיר טיפוס כללי אשר יוכל להצביע לכל סוג של אובייקט, נוכל להגדיר מחלקה אחת כללית של אוסף.
17
המחלקה Object
18
ב Java קיים טיפוס כללי אשר נקרא Object
ב Java קיים טיפוס כללי אשר נקרא Object. למעשה כל אובייקט ב- Java הוא גם Object. המשמעות העיקרית בשלב זה היא מצביע מטיפוס Object יכול להצביע על כל אובייקט מטיפוס אחר: Point p = new Point(2,3); Circle c = new Circle(p,3); Object obj = p; obj = c;
19
מה יקרה כאן. Point p = new Point(2,3); Object o1 = p; System. out
מה יקרה כאן? Point p = new Point(2,3); Object o1 = p; System.out.println(o1.getX()); x y getX() getY() p Point o1 Object שגיאת קופילציה כמובן, שכן לאובייקט כללי אין את השיטה getX(). מכיוון שאובייקט נוצר בפועל רק בזמן ריצה, מה שצריך לעשות הוא להבטיח לקומפיילר שבסופו של דבר המצביע אכן מצביע לאוביקט מסוג Point.
20
System.out.println(o1.getX());
מה יקרה כאן? Point p = new Point(2,3); Object o1 = p; System.out.println(o1.getX()); צריך להבטיח לקומפיילר שבסופו של דבר אותו מצביע יהיה מסוג Point, ע"י המרה (casting): System.out.println(((Point) o1).getX()); שגיאת קופילציה כמובן, שכן לאובייקט כללי אין את השיטה getX(). מכיוון שאובייקט נוצר בפועל רק בזמן ריצה, מה שצריך לעשות הוא להבטיח לקומפיילר שבסופו של דבר המצביע אכן מצביע לאוביקט מסוג Point.
21
זה מוביל למקרה הבא: Object o2=new Circle(p, 5); האם השורה הבאה חוקית
זה מוביל למקרה הבא: Object o2=new Circle(p, 5); האם השורה הבאה חוקית? ((Point)o2).getX(); חוקית לחלוטין, מכיוון שאנחנו "עבדנו" על הקומפיילר והבטחנו לו שבזמן ריצה המשתנה o2 יצביע על אובייקט מטיפוס Point. מה שיקרה בסופו של דבר, שבזמן ריצה התוכנית תקרוס. ניתן לאמר –"ברור שo2 מטיפוס Circle, הרי שורה לפני הגדרנו אותו!". על מנת להבהיר נקודה זו נסתכל בקטע הבא
22
נסתכל בקטע הבא: public static void func(Object o){ System. out
נסתכל בקטע הבא: public static void func(Object o){ System.out.println(((Point)o).getX()); } Main: Point p=new Point(2,3); Circle c=new Circle(p, 5); func(p); func(c); שימו לב, כי עתה לא ניתן יותר להגיד "ברור ש o2 מטיפוס Circle ". אז איך נפתור את הנקודה הזו? נצתרך מנגנון שיבדוק האם המשתנה שבידנו אכן מצביע בפועל על הטיפוס אליו אנחנו מתכוונים. ב java נשתמש בפקודה instanceof אשר תבצע בדיוק זאת:
23
variable instanceof type
מחזיר true אם המשתנה הוא מהטיפוס type דוגמה : Object s = "my string"; System.out.println(s instanceof String); System.out.println(s instanceof Point); true false
24
instanceof Point p = new Point(2,3); Circle c = new Circle(p, 5); Object o = c; (p instanceof Point)true p=null; (p instanceof Point)false (o instanceof Circle)true (o instanceof Point)false (c instanceof Object)true שימו לב כי הפקודה instanceof מחזירה false תמיד על מצביע שהוא null.
25
ולכן, נחזור לדוגמא הקודמת: public static void func(Object o){ if (o instanceof Point) System.out.println(((Point)o).getX()); } ועכשיו הכל בסדר.
26
שיטות המחלקה Object Object היא למעשה מחלקה בדיוק כמו כל מחלקה אחרת. למחלקה Object מספר שיטות. שיטות אלו "מתוספות" באופן אוטומטי לכל מחלקה אותה אנו כותבים. נראה שתיים מהן. השוואה בין שני אובייקטים: public boolean equals(Object other) המימוש במחלקה Object בעצם מבצע השוואת כתובות (==). המרת אובייקט למחרוזת: public String toString() המימוש במחלקה Object מחזיר את שם המחלקה של האובייקט והכתובת לו בזיכרון. נלמד על משמעות שיטות אלו והקשרן לחומר שלמדנו עד עתה בשיעור זה.
27
נסתכל בגרסה מופשטת של המחלקה Point: public class Point{ private double x; private double y; public Point(double x, double y){ this.x = x; this.y = y; } public double getX(){return x;} public double getY(){return y;} }//class In Main: Point p1 = new Point(1, 2); Point p2 = new Point(1, 2); System.out.println(p1.equals(p2));//??? שימו לב כי אין שיטה equals במחלקה Point, אך עדיין השורה עובדת (התשובה false כמובן), מכיוון ש"קיבלנו" את השיטה equals מהמחלקה Object. כנ"ל לגבי:
28
ראינו את הבעייתיות שנוצרה כאשר יצרנו את המחלקה PointSet.
נחזור עתה לבעייה שהובילה אותנו להכרות עם המחלקה Object – PointSet ו- Circle Set. ראינו את הבעייתיות שנוצרה כאשר יצרנו את המחלקה PointSet. כעת, יש לנו את הכלים לפתור את הבעיה: ניצור מחלקה חדשה אשר במקום לייצג קבוצה של אובייקטים מסוג מסוים, תייצג קבוצה של אובייקט מסוג כללי (Object). נתחיל לבנות את המחלקה
29
ObjectSet public class ObjectSet { private Object[] elements; private int size; // construct a set of given capacity public ObjectSet(int capacity){ elements = new Object[capacity]; size = 0; } כעת אנו יכולים לשמור במערך כל סוג של אובייקט. שימו לב שהשורה elements = new Object[capacity]; מאתחלת את המערך כך שכל הכניסות בו הן null.
30
ObjectSet public int size(){ return size; } public void add(Object o){ if (!contains(o) && size<elements.length){ elements[size] = o; size = size + 1; }//add 30
31
ObjectSet public void remove(Object o){ int i = indexOf(o); if (i != -1){ elements[i] = elements[size-1]; elements[size-1] = null; size = size - 1; } }//remove public boolean contains(Object o){ return indexOf(o) != -1; }//contains 31
32
ObjectSet private int indexOf(Object o){ for (int i=0; i<size; i=i+1){ if (elements[i].equals(o)){ return i; } return -1; } //indexOf האם זה עובד כמו שרצינו? השיטה equals המופעלת כאן היא השיטה שקיבלנו במתנה מהמחלקה Object, וכזכור המימוש המתקבל הוא ==, ואילו אנו רוצים שיוויון לוגי (לפי תוכן). היינו רוצים לשנות את המימוש של השיטה equals במחלקה Object שיתאים לצורך שלנו (למשל השוואה של Point). אבל א' אי אפשר (לא אנחנו כתבנו את המחלקה) וב' גם אם היה אפשר, זה היה הורס את כל המוטיבציה – לכל טיפוס אחר יש צורת השוואה אחרת. לשמחתנו, OOP בדיוק בא לפתור בעיות מסוג זה וכל מה שנו צריכים לעשות, זה להוסיף שיטה equals במחלקה המתאימה (Point או Student) 32
33
נוסיף שיטה equals במחלקה המתאימה :
public class Point{ … public boolean equals(Point other){ boolean ans = false; if (other != null){ ans = (x==other.x && y==other.y); } return ans; האם עכשיו זה יעבוד? לא, מכיוון שחתימת השיטה המופעלת היא כפי שהיא מופיעה במחלקה Object – public boolean equals(Object other), ואילו אנו מימשנו חתימה שונה! לכן, יש צורך לכתוב את השיטה equals בדיוק עם אותה חתימה:
34
התאמת חתימה: מה שכחנו? public class Point{ …
public boolean equals(Object other){ boolean ans = false; if (other != null){ ans = (x==other.x && y==other.y); } return ans; מה שכחנו?
35
דריסה (overriding) public class Point{ …
public boolean equals(Object other){ boolean ans = false; if (other instanceof Point){ ans = (x == ((Point)other).x && y == ((Point)other).y); } return ans; פעולה זו נקראת דריסה (overriding) – דרסנו שיטה שקיבלנו מ-Object בשיטה עם אותה חתימה אך מימוש שונה. שימו לב כי instanceof בודק גם null: השאלה האם "other instanceof Point" פרושה "האם המצביע other מצביע על אובייקט אשר בפועל הוא מטיפוס Point?", וכאשר other מצביע על null אזי התשובה היא – לא (כלומר, false).
36
אפשר לדרוס רק שיטות ציבוריות .
אי אפשר לדרוס שיטות סטטיות. אי אפשר לדרוס שיטות פרטיות.
37
נביט בקטע הבא: Point p=new Point(1,2); Object o=new Point(1,2);
(1) p.equals(o)?? (2) o.equals(p)?? פקודה (1) נראית די פשוטה – ברור שהשיטה equals של Point תופעל. אבל מה קורה עם (2)? אם תופעל השיטה של Object הרי שנקבל false, ואם תופעל השיטה של Point נקבל true. הרעיון ב OOP שנלמד אותו לעומק בהמשך, הוא שהשיטה שתתבצע בפועל נקבעת לפי האובייקט בזמן ריצה, ולכן פקודה (2) תפעיל את equals של המחלקה Point ונקבל true כמו שרצינו.
38
כעת אנו יכולים להשוות אובייקטים מכל סוג אל אובייקטים מסוג Point:
Point p1=new Point(2,3); Point p2=new Point(2,3); Circle c=new Circle(p1, 5); p1.equals(p2)true p1.equals(c)false
39
דוגמה: ObjectSet setP = new ObjectSet(100); Point p1=newPoint(1,1); Point p2=newPoint(3,4); setP.add(p1); setP.add(p2); System.out.println(setP.contains(p1));
40
נניח שהיינו רוצים להוסיף עוד שיטה למחלקה ObjectSet, אשר מדפיסה את כל האברים בה:
public void printElements(){ for (int i=0; i<size; i=i+1){ System.out.println(elements[i].toString()); } המימוש של המחלקה Object הוא להחזיר את שם המחלקה וכתובת האובייקט בזכרון. כאשר java מצפה לקבל מחרוזת, אך מקבל אובייקט כלשהוא, הוא מפעיל אוטומטית את השיטה toString שלו. מכיוון שאנו רוצים לממש לכל מחלקה ייצוג שונה, כל מה שאנו צריכים לעשות, זה לדרוס את השיטה toString (כמו שבעצם כבר עשינו). מה קורה בפועל? ישנן שתי פרוצדורות עם השם println (overloading): הראשונה מקבלת ארגומנט s מסוג String ומדפיסה אותו למסך, והשניה מקבלת ארגומנט o מסוג Object, ומפעילה את הראשונה עם הארגומנט o.toString().
41
האם זה יעבוד? public void printElements(){ for (int i=0; i<size; i=i+1){ System.out.println(elements[i]); } כאשר java מצפה לקבל מחרוזת, אך מקבל אובייקט כלשהוא, הוא מפעיל אוטומטית את השיטה toString שלו. מכיוון שאנו רוצים לממש לכל מחלקה ייצוג שונה, כל מה שאנו צריכים לעשות, זה לדרוס את השיטה toString (כמו שבעצם כבר עשינו). מה קורה בפועל? ישנן שתי פרוצדורות עם השם println (overloading): הראשונה מקבלת ארגומנט s מסוג String ומדפיסה אותו למסך, והשניה מקבלת ארגומנט o מסוג Object, ומפעילה את הראשונה עם הארגומנט o.toString().
42
במחלקה Point למשל: public String toString(){ return "("+x+","+y+")"; }
אפשר כמובן לדרוס את שיטת toString של Object במחלקה Point למשל: עכשיו השיטה printElements תעבוד כמו שצריך עבור המחלקה Point. שימו לב שכל מחלקה אשר אנו רוצים שתתפקד כמצופה, עליה לממש את כל אחת משתי השיטות שלמדנו: equals, ו toString. זאת הסיבה ששיטות אלו נקראות שיטות מיוחדות. עד כאן המחלקה Object. בהמשך הקורס נרחיב את ההבנה שלנו על היחס בין המחלקות השונות, ונבין יותר טוב את הקשר בין מחלקה מסוימת למחלקה Object. public String toString(){ return "("+x+","+y+")"; }
43
במחלקה Circle: public String toString(){
return "(“ + center.toString() + ",“ + radius +")"; } עכשיו השיטה printElements תעבוד כמו שצריך עבור המחלקה Point. שימו לב שכל מחלקה אשר אנו רוצים שתתפקד כמצופה, עליה לממש את כל אחת משתי השיטות שלמדנו: equals, ו toString. זאת הסיבה ששיטות אלו נקראות שיטות מיוחדות. עד כאן המחלקה Object. בהמשך הקורס נרחיב את ההבנה שלנו על היחס בין המחלקות השונות, ונבין יותר טוב את הקשר בין מחלקה מסוימת למחלקה Object.
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.