מדעי המחשב: מחלקות בJava מבוא.
[RIGHT]מחלקות בג'אווה[/RIGHT]
[RIGHT]החומר הזה הוא חומר מבוא לחומר של יחידות 4-5 במדעי המחשב. [/RIGHT]
[RIGHT]כידוע לכל, בשפה ג'אווה יש מספר טיפוסי נתונים, למשל int, double, char. נניח כי ברצוננו ליצור טיפוס חדש, שממלא צורך מסויים שלנו. למשל נניח שבצרוננו לבצע כל מיני פעולות עם דלאים, ובשביל זה נרצה שיהיה לנו משתנה מטיפוס דלי. לשם כך השפה ג'אווה מאפשרת יצירה של טיפוסי נתונים. יצירה זו מתבצעת באמצעות מושג המחלקה (class באנגלית).[/RIGHT]
[RIGHT]כאמור, המחלקה היא דבר שמאפשר ליצור עצמים (באנגלית object) מטיפוס מופשט שנתון לדמיונו הפרוע של המתכנת. בגדול, מה שמאפיין עצם זה שיש לו תכונות (attribute) ושיטות (method). התכונות, כפי שתראו בהמשך, הם משתנים (יכולים להיות מטיפוס פשוט כמו int, או שהם יכולים להיות עצמים בעצמם) ששומרים בתוכם ערכים החשובים לעצם. המתכנת קובע מה הוא ערך שחשוב לעצם. למשל בדוגמא לעיל בנוגע למחלקות מטיפוס דלי, נרצה שלדלי תהיה תכונה שאומרת מה הקיבולת שלו, ומה התכולה הנוכחית שלו. ייתכן שביישומים מסויימים נרצה גם שיהיה חשוב מה היא שנת הייצור של הדלי. במקרה הזה תהיה לדלי תכונה נוספת, שנת ייצור.
השיטות של עצם, זה הדברים שאנחנו רוצים לבצע איתו. למשל, נרצה למלות דלי, ונרצה לשפוך אותו לדלי אחר, ועל כן יהיו לנו מספר שיטות הקשורות לדברים האלה.
כמו כן, לכל עצם צריכה להיות פעולה שמייצרת עצמים כאלה. לפעולה זו קוראים בנאי (constructor).[/RIGHT]
[RIGHT]המבנה בגדול של מחלקה הוא[/RIGHT]
[code]
public class Example //מקובל לכתוב את שם המחלקה באות גדולה
{
Attributes……
Constructors …. (ייתכן ונרצה שיהיה יותר מבנאי אחד למחלקה)
Methods (פעולות של המחלקה)
}
[/code]
[RIGHT]לפני שנעסוק בבנייה למעשה של המחלקה, נראה איך להשתמש בה.
נניח שכתבתנו מחלקה שמייצגת דלי: Bucket. נניח שלמחלקה זו שני תכונות מטיפוס double, שקוראים להן _capacity ו currentAmount_. (על משמעות הקו התחתון נעמוד בהמשך) בנוסף, נניח שיש מספר בנאי, המקבל שני ערכים ממשיים, ומייצר דלי, שהקיבולת שלו זה הערך הראשון, והכמות הנוכחית שלו זה הערך השני. יש שיטה שנקראית fill המקבלת מספר ממשי, ומוסיפה אותו לדלי, אם אפשר (כלומר אם הכמות לא תעלה על קיבולת הדלי).[/RIGHT]
נניח שיש לנו תכנית main (מאותו סוג של תכניות בהן עסקנו בעבר, בחומר של יחידות 1-2). נכתוב בה את השורות הבאות:
[code]
Bucket buck = new Bucket(3.2,1.1); //שימוש בבנאי של המחלקה. מזכיר מאוד את סקאנר.
System.out.println(buck._capacity); //קריאה לתכונת הקיבולת של הדלי. יודפס הערך 3.2
buck.fill(2); //קריאה לשיטת המילוי של הדלי.
System.out.println(buck._currentAmount); //קריאה לתכונת הכמות הנוכחית של הדלי. יודפס הערך 3.1
buck.fill(1); //ממלאים יותר ממה שאפשר.
System.out.println(buck._currentAmount); //יודפס הערך 3.2, כי זה הכמות המקסימלית של מים בדלי.
[/code][RIGHT]שימו לב, שבשורות אלו ניגשנו אל התכונות של האובייקט בצורה ישירה, דרך נקודה. בדרך כלל לא נעשה זאת. הסיבה לכך היא שלרוב נרצה לעשות בדיקות תקינות על התכונות (למשל קיבלות לא יכולה להיות שלילית), ואם ניתן למשתמש גישה חופשית אל משתנים אלו, יוכל לעשות בהן כרצונו, ובכך התוצאות שלו עלולות להיות לא הגיוניות, כי הקוד שלנו מסתמך על כך שהקיבולת זה מספר אי שלילי.[/RIGHT]
[RIGHT]לכן, ברצוננו למנוע מהמשתמש "לגעת" במשתנים האלו. נעשה את זה באמצעות מאפייני גישה (access modifier). נסביר זאת מיד כשנבנה את המחלקה.
כמו כן, לכל מחלקה יש ממשק. ממשק הוא למעשה התמונה החיצונית של המחלקה, והוא אוסף הדברים שאנו נותנים למשתמש להשתמש בהם. כל מה שאינו בממשק, המשתמש בכלל לא אמור לדעת על קיומו. הממשק מתאר את המחלקה ואת פעולותיה. נניח שברצוננו שלמחלקה Bucket יהיה הממשק הבא: (שימו לב שהמבנה של הפעולות הוא כמו המבנה של הפעולות אותן בניתם ביחידות 1-2, יש שם, ואז פרמטרים עם טיפוס)[/RIGHT]
Bucket(double capacity, double amountToFill) – זהו הבנאי, שאת פעולתו הסברנו קודם
[code]
[RIGHT]void fill(double amountToFill) – הפעולה ממקודם.
double getCapacity() – מחזיר את הקיבולת הנוכחית
[RIGHT]double getCurrentAmount() – מחזיר את כמות המים הנוכחית
void setCapacity(double newCapacity) – משנה את הקיבולת לערך שהוכנס
void pourInto(Bucket bucketInto) – שופכת את הכמות המקסימלית של מים שאפשר מהדלי הזה, לדלי שהתקבל כפרמטר.[/RIGHT]
[/RIGHT]
[/code][RIGHT][RIGHT]בנוסף, ברוב המחלקות של ג'אווה מקובל שתהיה שיטה עם שם סטנדרטי[/RIGHT]
[RIGHT]public String toString()[/RIGHT]
[RIGHT](המילה public היא מאפיין גישה, ומיד נדון במשמעותה)
זוהי פעולה שמחזירה מחרוזת, שבדרך כלל מתארת את האובייקט. (למשל במקרה שלנו, פעולה זו תחזיר את קיבולת הדלי, ואת כמות המים בו). אם היינו רוצים להדפיס את האובייקט, היינו צריכים לרשום[/RIGHT]
[/RIGHT]
[code]
[RIGHT]System.out.println(buck.toString())[/RIGHT]
[/code][RIGHT][RIGHT]אך הפעולה toString שהשם שלה הוא זה שתואר למעלה מיוחדת: אין צורך לקרוא לה. כלומר אם נכתוב[/RIGHT]
[/RIGHT]
[code]
[RIGHT]System.out.println(buck)[/RIGHT]
[/code][RIGHT][RIGHT]המחשב יבין לבד שמדובר בפעולה toString וייקרא לה. אם [B]לא [/B]הוגדרה פעולה toString, המחשב ידפיס את התא בזכרון בו נשמר האובייקט, כי זה toString של מחלקה בסיסית יותר, שכל המחלקות מבוססות עליה, אך אין אנו נרחיב על זה את הדיון בשלב הזה. [/RIGHT]
[RIGHT]כעת לבניית המחלקה. נלך על פי המבנה הכללי שתואר.
נתחיל:[/RIGHT]
[/RIGHT]
[code]
[RIGHT]public class Bucket
{
כאן צריכות להיות תכונות
}[/RIGHT]
[/code][RIGHT][RIGHT]לתכונות רצינו לקרוא _capacity, _currentAmount, הן צריכות להיות double. לכן נכתוב זאת כך:[/RIGHT]
[/RIGHT]
[code]
[RIGHT]public class Bucket
[RIGHT]{
private double _capacity, _currentAmount; //דומה להצהרת משתנים בתחילת תכנית.
}[/RIGHT]
[/RIGHT]
[/code][RIGHT][RIGHT][RIGHT]המילה private היא מאפיין גישה, שפירושה שמותר להשתמש במשתנה הזה רק בתוך המחלקה. כלומר אם ננסה להשתמש בקוד ממקודם, תהיה לנו שגיאה שטוענת שהמחשב לא מכיר את המשתנה _capacity.[/RIGHT]
[/RIGHT]
[RIGHT]כעת לבנאי. הבנאי אמור לקבל שני ערכים, ולהפוך את הערכים האלה לתכונות של האובייקט. נבצע זאת כך[/RIGHT]
[/RIGHT]
[code]
[RIGHT]public Bucket(double capacity, double currentAmount)
{
_capacity = capacity;
_currentAmount = currentAmount;
{[/RIGHT]
[/code]
[RIGHT]שימו לב, ששם הבנאים במחלקה מסויימת [U]תמיד[/U] יהיה זהה לשם המחלקה.
[RIGHT]כעת אנו מבינים מדוע הוספנו את ה_ בתחילת המשתנים. אם היינו נותנים לתכונות את השמות capacity וcurrentAmount, היה לכאורה נוצר מצב של בלבול, שבתוך הבנאי המחשב אינו יודע אם התכוונו למשתנה capacity שהתקבל כפרטמר, או לתכונה capacity. אבל גם במקרה זה למחשב יש פתרון. נניח שקראנו לתכונות capacity ו currentAmount. במקרה זה, אנחנו רוצים לגשת לתכונות של המשתנה הנוכחי.
לו זה היה בתכנית הראשית, והמשתנה היה public יכולנו פשוט לעשות[/RIGHT]
[RIGHT][code]buck.capacity = capacity;[/code][/RIGHT]
[RIGHT]וכאן לא היה נוצר כפל משמעות, כי הכוונה באגף שמאל זה למשתנה capacity של buck, ובאגף ימין זה משתנה capacity עצמאי.
נרצה לעשות משהו דומה, אך אין לנו את השם של האובייקט הנוכחי. ג'אוורה בשביל זה המציאה את המילה this. המילה this, זה משתנה, ששומר בתוכו את האובייקט [B]עליו[/B] [B]מדובר.[/B] כלומר, הבנאי במקרה זה יראה כך:[/RIGHT]
[/RIGHT]
[code]
[RIGHT]public Bucket(double capacity, double currentAmount)
{
this.capacity = capacity;
this.currentAmount = currentAmount;
}[/RIGHT]
[/code][RIGHT][RIGHT]לכן הוספה של _ לפני שם התכונה מקל מעט על העבודה, חוץ מזה שזוהי מוסכמה מקובלת בעולם מדעי המחשב. [/RIGHT]
[RIGHT]כעת ניגש לפעולות. נבחן פעולה אחת, רוב השאר דומות.[/RIGHT]
[/RIGHT]
[code]
[RIGHT]public void fill(double amountToFill)
{
double possibleAmount = _currentAmount + amountToFill;
_currentAmount = Math.min(_capacity, possibleAmount); // אם אינך שגור לשימוש במחלקה בפעולות המתמטיות, אז במקום שורה זו בצע את ארבע השורות הבאות:
if(possibleAmount > _capacity)
_currentAmount = _capacity;
else
_currentAmount = possibleAmount;
}[/RIGHT]
[/code][RIGHT][RIGHT]שימו לב, בשונה מif וfor, גם אם פעולה מכילה רק שורה אחת, [B]חובה[/B] לשים שורה זו בין { ל- }. נדגים גם את הפעולה toString:[/RIGHT]
[/RIGHT]
[code]
[RIGHT]public String toString()
{
return “The capacity is:” + _capacity + “ and the current amount is: “ + _currentAmount;
}[/RIGHT]
[/code]
[RIGHT]המחלקה כולה תראה אם כן כך:[/RIGHT]
[code]
[RIGHT]public class Bucket
[RIGHT]{
private double _capacity, _currentAmount;
public Bucket(double capacity, double currentAmount){…}
public void fill(double amountToFill){…}
public double getCapacity(){..}
public double getCurrentAmount(){…}
public void setCapacity(double newCapacity){…}
public void pourInto(Bucket bucketInto) {…}
public String toString() {…}
במקום ... בכל פעולה צריך כמובן להיות המימוש שלה.
}[/RIGHT]
[/RIGHT]
[/code]
[RIGHT][RIGHT][I]תרגיל[/I][/RIGHT]
[RIGHT]נניח שבתכנית הראשית כתובות השורות:[/RIGHT]
[/RIGHT]
[code]
[/code]
[code]
[RIGHT]Bucket buck1 = new Bucket(3.3, 1.3);
Bucket buck2 = new Bucket(7.5, 6);
[RIGHT]buck2.setCapacity(10);
buck1.fill(2);
buck1.pourInto(buck2);
System.out.println(buck1);
System.out.println(buck2);[/RIGHT]
[/RIGHT]
[/code][RIGHT][RIGHT][RIGHT]מה הפלט?[/RIGHT]
[/RIGHT]
[RIGHT][I]תשובה:[/I]
[RIGHT]The capacity is 3.3 and the current amount is 0
The capacity is 10 and the current amount is 9.3[/RIGHT]
[/RIGHT]
[RIGHT][I]מטלה (ללא פתרון)[/I]
[RIGHT]ברצוננו לבנות את המחלקה Point שתייצג נקודה במערכת צירים x-y. לנקודה יהיו שני מאפיינים, שיעור x ושיעור y. לנקודה יהיו שיטות get ו set דומות לאלו של Bucket: לכל תכונה תהיה שיטה get שמחזירה את הערך הנוכחי, ושיטת set שמשנה את הערך הנוכחי לפרמטר שהוכנס. את הנקודה יהיה ניתן להזיז במישור באמצעות שיטה move שתקבל שני פרמטרים ממשיים, שמייצגים בכמה יש להזיז את הנקודה ימינה ולמעלה (אם הם שליליים אז זה יהיה שמאלה ולמטה בהתאמה). למחלקה תהיה שיטה toString שיחזיר את הנקודה בצורה(x,y).
[RIGHT]נסו לכתוב מחלקה זו.
אם תתקשו תוכלו לבקש עזרה בנושא זה.
נכתב מהראש שלי, הדוגמא על Bucket לקוחה מהספר "עיצוב תכנה מבוסס עצמים בשפת ג'אווה" של האוניברסיטה העברית
כל הזכויות שמורות ללורד אביזי, ואין להעתיק זאת ללא אישור מפורשת וחתימת קיסר מאדים.
יום נעים.[/RIGHT]
[/RIGHT]
[/RIGHT]
[/RIGHT]