המשך תכנות מונחה עצמים תרגול מס' 9
היום בתרגול this Deep Copy Shallow Copy, הכמסה (Encapsulation) הגבלת גישה (visibility modifiers) Getters ו- Setters חריגות (Exceptions)
תזכורת: מחלקות ואובייקטים הנה המחלקה MyString: public class MyString { //class fields public char[] elements; public int length; //class method public int length(){ return length; } שדות – מייצגים מצב של האובייקט שיטה – מתארת התנהגות של האובייקט
תזכורת: מחלקות ואובייקטים על מנת ליצור אובייקט מסוג MyString, נשתמש במילה new: אך כיצד ניתן לשים ערכים בתוך המחרוזת שלנו? לשם כך נגדיר בנאי, שבעזרתו נאתחל את שדה האובייקט MyString s = new MyString();
תזכורת: בנאי (constructor) בנאי – שיטה מיוחדת עבור מחלקה שתפקידה לייצר אובייקט ולאתחל את שדותיו ועכשיו נוכל לאתחל אובייקט מסוג MyString ישירות: public class MyString { public char[] elements; public int length; public MyString(String s){ length = s.length(); elements = new char[length]; for (int i = 0; i < s.length(); i++) { elements[i] = s.charAt(i); } … השם זהה לשם המחלקה ואין ערך החזר MyString s = new MyString("bimba");
תזכורת: בנאי (constructor) נוסיף בנאים אחרים: public class MyString { //class fields public char[] elements; public int length; //constructor with a parameter public MyString(char[] otherElements){ length = otherElements.length; elements = new char[length]; for (int i = 0; i < otherElements.length; i++) { elements[i] = otherElements[i]; } ...
תזכורת: בנאי ברירת מחדל (default constructor) ברגע שמגדירים בנאי כלשהו, לא יסופק לנו בנאי חסר פרמטרים מה יקרה כאשר נבצע: public class MyString { //class fields public char[] elements; public int length; //constructor with a parameter public MyString(char[] otherElements){ … } MyString s1 = new MyString(); שגיאת קומפילציה
תזכורת: בנאי מעתיק (copy constructor) משתמשים בבנאי זה באופן הבא: public MyString(MyString other) { length = other.length; elements = new char[length]; for (int i = 0; i < length; i=i+1) elements[i] = other.elements[i]; } מעתיקים את השדות של האובייקט (העתקת המצב) MyString s1 = new MyString(“Bob”); MyString s2 = new MyString(s1);
מה היתרון בשימוש בדרך זו? this ראינו כי ניתן להגדיר מספר בנאים למחלקה בעזרת העמסה (Overloading) ניתן לקרוא מבנאי אחד לבנאי אחר בעזרת המילה השמורה: לדוגמה: הקריאה חייבת להתבצע בשורה הראשונה של הבנאי this public MyString(MyString other){ this(other.elements); } ( ) מה היתרון בשימוש בדרך זו?
this דוגמה נוספת: מה עושה הבנאי השני? public class MyString{ public char[] elements; public int length; public MyString(String s) { … } public MyString() { this("");
this ניתן להשתמש ב - this על מנת להבדיל בין שדה לבין פרמטר או משתנה לוקאלי בעלי אותו שם . לדוגמה: בעזרת האופרטור (.) public class MyString{ public char[] elements; public int length; public MyString(char[] elements, int length) { this.length = length; this.elements = new char[this.length]; for (int i = 0; i < this.length; i=i+1) this.elements[i] = elements[i]; }
Deep Copy ו- Shallow Copy תזכורת (מההרצאות) – המחלקות Point ו- Circle: public class Point { public double x; public double y; public Point() { x = 0; y = 0; } public Point(Point p) { x = p.x; y = p.y; בנאי חסר פרמטרים בנאי מעתיק
Deep Copy ו- Shallow Copy public class Circle{ public Point center; public double radius; //constructors public Circle() { center = new Point(); radius = 0; } public Circle(Point cen, double rad) { center = new Point(cen); if (rad >= 0) radius = rad; else rad = 0; … }//Circle בנאי חסר פרמטרים בנאי המקבל את כל השדות
Deep Copy ו- Shallow Copy בנאי מעתיק אפשרי ל- Circle: מה יקרה אם נבצע את הפעולות הבאות: הבנאי המעתיק משתמש בגישת ה- Shallow Copy. public Circle(Circle other) { center = other.center; radius = other.radius; } Copy constructor public static void main(String[] args){ Circle circ1 = new Circle(); Circle circ2 = new Circle(circ1); circ2.center.x = 4; }
Deep Copy ו- Shallow Copy בנאי מעתיק אחר ל- Circle: מה יקרה עכשיו אם נבצע את הפעולות הבאות: הבנאי המעתיק הזה משתמש בגישת ה- Deep Copy. public Circle(Circle other) { center = new Point(other.center); radius = other.radius; } Circle circ1 = new Circle(); Circle circ2 = new Circle(circ1); circ2.center.x = 4;
toString() equalsהשיטה היינו רוצים לבצע השוואה לוגית בין שני אובייקטים תזכורת: ישנן שיטות המשותפות לכל האובייקטים. למשל... היינו רוצים לבצע השוואה לוגית בין שני אובייקטים על מנת לעשות זאת נשתמש בשיטה equals (כמו שראינו ב- String) שיוויון לוגי אינו בהכרח השוואה של כל שדות האובייקט toString()
מחזיר true רק אם other אינו Null ומטיפוס Point equalsהשיטה מחזיר true רק אם other אינו Null ומטיפוס Point נממש מחדש את השיטה equals עבור המחלקה Point public class Point { public double x; public double y; public boolean equals(Object other){ boolean ans = false; if (other instanceof Point){ Point otherPoint = (Point)other; if (x==otherPoint.x && y==otherPoint.y) ans = true; } return ans;
השוואה בין כתובות (הערך בטבלת המשתנים) equals השיטה אם לא היינו ממשים את equals במחלקה Point , Java מספק לנו שיטת equals אשר משווה בין כתובות. public boolean equals(Object other){ return this == other; } השוואה בין כתובות (הערך בטבלת המשתנים)
הכמסה (encapsulation) עקרון המאפשר לאובייקט להכיל את המצב שלו ואת השיטות הקשורות לו. הרעיון: הצגת היכולות של אובייקט ע"י השיטות שמוגדרות כ- public, וללא הצגת המבנה הפנימי של האובייקט (כיצד הוא בנוי) המשתמש לא צריך לדעת כיצד האובייקט עובד, אלא רק מה הוא עושה רעיון זה דומה לרעיון שפונקציה מבצעת פעולות מסוימות, ומי שמשתמש בפונקציה, לא צריך לדעת כיצד היא ממומשת
הכמסה (encapsulation) מה משיגים בעזרת הכמסה והגבלת גישה: שמירה על חוקיות המצב של האובייקט – ניתן לקבוע אילו ערכים מייצגים מצב חוקי ואילו לא פשטות – מי שכותב את המחלקה קובע מה המשתמש צריך לדעת על האובייקט ומה לא הגנה על המידע הפנימי באובייקט – ניתן להבטיח שאף אחד לא יוכל לקרוא את הערכים הפנימיים של האובייקט גמישות במימוש ובשינוי המימוש
Getters ו- Setters על מנת להבטיח את עקרון ההכמסה נשתמש בשיטות על מנת לחשוף את המצב של האובייקט getter – שיטה אשר מחזירה ערך של שדה מסוים setter – שיטה אשר קובעת ערך של שדה מסוים בעזרת השיטות האלו נוכל לשנות את מצב האובייקט, תוך כדי שמירה על עקרון ההכמסה
Getters ו- Setters דוגמה – קביעת מהירות במכונית: public class Car { public final int MAX_SPEED = 210; public final int MIN_SPEED = -20; public int speed; public int getSpeed() { return speed; } public void setSpeed(int speed) { if ((speed >= MIN_SPEED) && (speed <= MAX_SPEED)){ this.speed = speed; קבועים של המחלקה getter setter
Visibility Modifiers מה הבעיה בדוגמה הקודמת? השדה speed מוגדר כ- public לכן עדיין ניתן לשנות את ערכו מחוץ למחלקה, לדוגמה: public class Car { … public int speed; } Car car1 = new Car(); car1.speed = 500;
Visibility Modifiers ב- Java, ניתן להגביל גישה לשדה בעזרת שינוי ה- visibility modifier של השדה כבר ראינו שימוש ב- visibility modifier: public, אשר מאפשר גישה לשדה לכולם (גם מחוץ למחלקה) ישנו visibility modifier נוסף: private אשר מגביל את הגישה לשדות, כך שרק שיטות של המחלקה יוכלו לגשת אליו
Visibility Modifiers מה יקרה בדוגמה הבאה: כאשר ננסה לגשת לשדה מחוץ למחלקה? נקבל שגיאת קומפילציה public class Car { … private int speed; } Car car1 = new Car(); car1.speed = 500;
Visibility Modifiers ראינו כי ניתן להגביל גישה לשדה, אך ניתן להגביל גישה גם עבור שיטות של המחלקה ניתן לקבוע חלק מהשיטות כ- private (במקום public) שיטה שמוגדרת כ- private משמשת כשיטת עזר לביצוע פעולות פנימיות של המחלקה
Visibility Modifiers public double getPrice() { public class Car { … //we are not FRAIERIM public double getPrice() { return getMarketPrice()*1.1; } private double getMarketPrice(){ ...
Getters ו- Setters לא תמיד נרצה להוסיף Getter ו- Setter לכל שדה במחלקה לדוגמה: אם מגדירים שמכונית מקבלת צבע בעת יצירתה, ואין אפשרות לשנותו, אך עדיין רוצים לאפשר להחזיר את הצבע public class Car { private String color; … public Car(String color) { this.color = color; } public String getColor() { return color; הגדרנו רק Getter (ללא Setter)
Getters ו- Setters דוגמה נוספת: נניח שישנו קוד הפעלה למכונית, ולא נרצה לחשוף אותו public class Car { private String code; … public Car(String initialCode) { code = initialCode; } public void startCar(String code) { if (!code.equals(this.code)) System.out.println("Wrong code"); else
הגדרנו רק Setter (ללא Getter) Getters ו- Setters (המשך משקף קודם) public class Car { … public void setNewCode(String oldCode, String newCode) { if (!oldCode.equals(code)) System.out.println(“Wrong code”); else code = newCode; } הגדרנו רק Setter (ללא Getter)
חריגות (Exceptions) חריגה היא אירוע המתרחש במהלך תוכנית המפר את תהליך הריצה הנורמאלי של פקודות התוכנית לעיתים חריגות מתרחשות בגלל תקלות בלתי צפויות, כגון בעיה בקובץ אליו כותבים (למשל אין הרשאות כתיבה), ולעיתים בגלל תקלות תוכנה, כגון שליחת פרמטר לא מתאים לפונקציה 31
בעצם כבר נתקלנו ב-Exceptions ArithmeticException: ניסיון חלוקה באפס IndexOutOfBoundsException: חריגה ממערך NullPointerException: ניסיון לפעול על מערך בעל ערך null Runtime Exceptions 32
NullPointerException סוגי Exceptions Exception IOException RuntimeException NullPointerException 33
Exception ניתן לייצר חריגה ע"י פקודת throw המייצרת את אירוע החריגה. ישנן שתי דרכים לטפל בחריגה: לתפוס את ה- Exception על ידי שימוש במילים השמורות try ו-catch להעביר את ה- Exception הלאה על ידי שימוש במילה השמורה throws בכותרת הפונקציה שאנו כותבים. (לא הכרחי עבור (RuntimeExceptions
Throw and Catch Exceptions public class Car { private final int MAX_SPEED = 210; private final int MIN_SPEED = -20; private int speed; … public void setSpeed(int speed){ if ((speed >= MIN_SPEED) & (speed <= MAX_SPEED)) this.speed = speed; else throw new RuntimeException(“illegal speed”); } public static void main(String[] args) { Car car = new Car(); car.setSpeed(300); } Output: Exception in thread "main" java.lang.RuntimeException: Illegal Speed at Car.setSpeed(Car.java:11) at Car.main(Car.java:17)
Throw and Catch Exceptions public class Car { private final int MAX_SPEED = 210; private final int MIN_SPEED = -20; private int speed; … public void setSpeed(int speed) throws Exception { if ((speed >= MIN_SPEED) & (speed <= MAX_SPEED)) this.speed = speed; else throw new Exception(“illegal speed”); } public static void main(String[] args) { Car car = new Car(); car.setSpeed(100); } Compilation Error
Throw and Catch Exceptions public class Car { private final int MAX_SPEED = 210; private final int MIN_SPEED = -20; private int speed; … public void setSpeed(int speed) throws Exception { if ((speed >= MIN_SPEED) & (speed <= MAX_SPEED)) this.speed = speed; else throw new Exception(“illegal speed”); } public static void main(String[] args) { Car car = new Car(); try{ car.setSpeed(300); } catch(Exception e){ System.err.println("Caught Exception: " + e.getMessage()); Output: Caught Exception: Illegal Speed