תכנות מתקדם ב-Java/אובייקטים: הבדלים בין גרסאות בדף

מתוך ויקיספר, אוסף הספרים והמדריכים החופשי
תוכן שנמחק תוכן שנוסף
Johnny Zoo (שיחה | תרומות)
מ בעבודה
Johnny Zoo (שיחה | תרומות)
אין תקציר עריכה
שורה 61: שורה 61:


===תוכנית המכולת===
===תוכנית המכולת===
נראה כאן מימוש בג'אווה של ה"מכולת" אותה תיארנו קודם. נעבוד עם שני קבצים: {{קוד בשורה|Item.java}} יכיל את המחלקה המטפלת במוצרים. {{קוד בשורה|Stock.java}} יכיל את המחלקה המטפלת במלאי.
נראה כאן מימוש בג'אווה של ה"מכולת" אותה תיארנו קודם. נעבוד עם שלושה קבצים: {{קוד בשורה|Item.java}} יכיל את המחלקה המטפלת במוצרים. {{קוד בשורה|Stock.java}} יכיל את המחלקה המטפלת במלאי. הקובץ השלישי אינו חלק מהמימוש עצמו, אך הוא יראה כיצד מחלקה אחרת ניגשת אל המחלקות שיצרנו ועושה בהן שימוש.
<source lang = "java">
<source lang = "java">
// Item.java
// Item.java
שורה 68: שורה 68:
// Item's name
// Item's name
String _name;
private String _name;
// Item's description
// Item's description
String _description;
private String _description;
// Item's price
// Item's price
double _price;
private double _price;
// Quantity of that item
// Quantity of that item
int _quantity;
private int _quantity;
// Days left for that item until expired
// Days left for that item until expired
int _daysLeft;
private int _daysLeft;
/*
/*
שורה 97: שורה 97:
public void setQuantity(int newQuantity) {
public void setQuantity(int newQuantity) {
_quantity = newQuantity;
_quantity = newQuantity;
}
// Get item's name
public String getName() {
return _name;
}
}
שורה 102: שורה 107:
public double getPrice() {
public double getPrice() {
return _price;
return _price;
}
// Get item's quantity
public int getQuantity() {
return _quantity;
}
}
שורה 119: שורה 129:


public class Stock {
public class Stock {
public final int MAX_ITEMS = 10;
// Array that holds all items in the stock
// Array that holds all items in the stock
Item[] _stock;
private Item _item1;
private Item _item2;
/*
/*
שורה 129: שורה 138:
*/
*/
public Stock() {
public Stock() {
_item1 = null;
// Build the array of items
_item2 = null;
_stock = new Item[MAX_ITEMS];
}
}
// Add item to stock
// Add item to stock
public void addItem(Item it, int serial) {
public void addItem(Item it) {
if(_stock[serial] != null) {
if(_item1 == null)
_item1 = it;
System.out.println("Cannot add item: this cell is full");
else if(_item2 == null)
return;
_item2 = it;
}
else
_stock[serial] = it;
System.out.println("Stock is full, cannot add "+it.getName());
}
}
// Print all items in stock
// Print all items in stock
public void printStock() {
public void printStock() {
if(_item1 != null) _item1.printItem();
for(int i=0; i<_stock.length; i++) {
if(_stock[i] != null)
if(_item2 != null) _item2.printItem();
_stock[i].printItem();
}
}
}
שורה 153: שורה 161:
public double sumStock() {
public double sumStock() {
double sum = 0.0;
double sum = 0.0;
if(_item1 != null)
for(int i=0; i<_stock.length; i++) {
sum+=_item1.getPrice()*_item1.getQuantity();
if(_stock[i] != null)
if(_item2 != null)
sum+=_stock[i].getPrice();
sum+=_item2.getPrice()*_item2.getQuantity();
}
return sum;
return sum;
}
}
// Set new quantity for that item
// Set new quantity for that item
public void setItemQuantity(int serial, int newQuantity) {
public void setItemQuantity(String itemName, int newQuantity) {
if(_item1.getName().equals(itemName))
if(_stock[serial] == null) {
_item1.setQuantity(newQuantity);
System.out.println("No such item!");
else if(_item2.getName().equals(itemName))
return;
_item2.setQuantity(newQuantity);
else {
System.out.println("No such item: "+itemName);
}
}
_stock[serial].setQuantity(newQuantity);
}
}
}
</source>
הקלה נוספת לשם הפשטות: המלאי יכול להכיל רק עד שני מוצרים. זהו, כמובן, מימוש לא שימושי במיוחד, והוא נעשה לצרכי הדגמה בלבד. נראה כאן את המחלקה Grocery, שתשתמש במחלקות שיצרנו על מנת לנהל את המלאי:
<source lang = "java">
// Grocery.java

public class Grocery {
public static void main(String[] args) {
// Build a stock object
Stock stck = new Stock();
// Print the stock - it is empty now
stck.printStock();
// Add items to stock:
Item it1 = new Item("Cheese","Smelly green cheese", 1.5, 2, 7);
Item it2 = new Item("Tomato","Fresh tomamto", 2.6, 25, 10);
stck.addItem(it1);
stck.addItem(it2);
stck.printStock();
System.out.println("Total price of stock: " + stck.sumStock());
// Try to insert third item to stock:
Item it3 = new Item("New item","",0.0,0,0);
stck.addItem(it3);
// Change quantity of cheese:
stck.setItemQuantity("Cheese", 200);
// Change quantity of item that does not exist:
stck.setItemQuantity("Bread", 1000);
System.out.println("Now total price of stock is "+stck.sumStock());
}

}
}
</source>
</source>

גרסה מ־23:22, 19 בדצמבר 2007

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


אובייקטים או עצמים, הם אבני היסוד של התכנות מונחה העצמים.

הקדמה - תכנות מונחה עצמים

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

דוגמה - מכולת

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

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

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

מבנה

כל טיפוס של אובייקט נקרא "מחלקה" (Class). מחלקה מכילה בנאים (והורסים), מתודות/שיטות (Methods) ומשתנים פנימיים. בדוגמה שלנו, קיימות שתי מחלקות: מלאי, ומוצר. המלאי מכיל את השיטות "חשב ערך כולל", "הדפס מלאי", "הוסף מוצר למלאי", וכן הלאה. הוא מכיל את הנתונים לגבי המוצרים: מערך מטיפוס "מוצר". המוצר יכיל כמה שיטות משלו: "הדפס מוצר", "צור מוצר חדש", וכדומה. הוא יכיל משתנים פנימיים: משתנה שיכיל בתוכו את מחיר המוצר, משתנה שיכיל את שם המוצר, וכן הלאה.

בנאים

בנאי (Constructor), כשמו, הוא כלי באמצעותו יוצרים אובייקט. לכל אובייקט קיים בנאי, ולפעמים גם כמה סוגים של בנאים. חוץ מיצירת האובייקט, הבנאי יכול להכיל פעולות כאוות נפשו של המתכנת. הערה: בניגוד לשפות (כמו C++), בהן קיים צורך להשמיד (Destruct) כל אובייקט לאחר השימוש בו כדי לפנות את הזיכרון בו נעשה שימוש, בג'אווה אין צורך בכך - כלי בשם Garbage Collector (אוסף האשפה) אחראי על פינוי הזיכרון, ומסיר בכך את הנטל מעל כתפי המתכנתים. נעיר כי העדפה זו של הנוחות עולה במחיר מה של מהירות הריצה.

משתנים פנימיים

משתנים פנימיים הם המשתנים אותם מכיל האובייקט בתוכו. הם יכולים להיות מכל סוג שהוא - משתנים פשוטים כמו int או String, מערכים, או אף אובייקטים אחרים. לעיתים קרובות הם מכונים גם "שדות".

שיטות

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

איך זה נראה בג'אווה

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

בנאי

הבנאי הוא שיטה ששמה כשם המחלקה, ושאינה מחזירה ערך משום סוג. אם נניח ששם המחלקה הוא MyClass, הבנאי יראה כך:

public MyClass() {
	// Some code
}

בנאי יכול לקבל ערך, או ערכים רבים, מכל סוג, בצורה הבאה:

public MyClass(int x, float y) {
	// Some code
}

הקריאה לבנאי מתבצעת בעזרת המילה השמורה new, בצורה הבאה: MyClass obj = new MyClass(); או, אם הבנאי מקבל ערכים, למשל - בנאי שמקבל int ו-float: MyClass obj = new MyClass(1, 2.3); מרבית הקוראים בוודאי יתמהו: מדוע להכריז בצורה כזו על האובייקט? מדוע שלא להכריז על אובייקט כמו שמכריזים על משתנה פשוט - MyClass obj;? למען האמת, מורכבת ההכרזה שהצגנו כאן משני חלקים. החלק הראשון, הימני - MyClass obj מקצה מקום בזיכרון עבור אובייקט מטיפוס MyClass. המחשב מקצה את המקום הדרוש, אך לא יוצר בו אובייקט. החלק השני, השמאלי, בו אנו פונים לבנאי, הוא החלק שיוצר את האובייקט, במקום שכבר הוקצה עבורו בזיכרון. כשתלמדו כמה צדדים מתוחכמים יותר של התכנות מונחה העצמים תוכלו להבין טוב יותר מדוע קיימת ההפרדה הזו.

משתנים ושיטות

צורת העבודה עם המשתנים והשיטות דומה מאוד לזו שכבר הכרנו, אך ללא המילה static. בעבודה עם אובייקטים נהוג להשתמש במשתנים גלובליים - של האובייקט.

פנייה למשתנים ושיטות

נקודת ההסתכלות צריכה לעבור מהפך, כאשר מתעסקים בתכנות מונחה עצמים. כעת, כל פעולה שמתבצעת - מתבצעת על אובייקט. כדי לבצע פעולה כלשהי על אובייקט, משתמשים בצורה obj.action(arguments) כדי לבצע את השיטה action (עם הארגומנטים arguments) על האובייקט obj, או obj.variable כדי לפנות למשתנה variable, שהוא משתנה פנימי של האובייקט obj.

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

תוכנית המכולת

נראה כאן מימוש בג'אווה של ה"מכולת" אותה תיארנו קודם. נעבוד עם שלושה קבצים: Item.java יכיל את המחלקה המטפלת במוצרים. Stock.java יכיל את המחלקה המטפלת במלאי. הקובץ השלישי אינו חלק מהמימוש עצמו, אך הוא יראה כיצד מחלקה אחרת ניגשת אל המחלקות שיצרנו ועושה בהן שימוש.

// Item.java

public class Item {
	
	// Item's name
	private String _name;
	// Item's description
	private String _description;
	// Item's price
	private double _price;
	// Quantity of that item
	private int _quantity;
	// Days left for that item until expired
	private int _daysLeft;
	
	/*
	 * Constructor
	 */
	public Item(String name, String desc, double price, int quantity, int days) {
		_name = name;
		_description = desc;
		_price = price;
		_quantity = quantity;
		_daysLeft = days;
	}
	
	// Set days left until expiration
	public void SetDaysLeft(int newDaysLeft) {
		_daysLeft = newDaysLeft;
	}
	
	// Set item's quantity
	public void setQuantity(int newQuantity) {
		_quantity = newQuantity;
	}
	
	// Get item's name
	public String getName() {
		return _name;
	}
	
	// Get item's price
	public double getPrice() {
		return _price;
	}
	
	// Get item's quantity
	public int getQuantity() {
		return _quantity;
	}
	
	// Print item details
	public void printItem() {
		System.out.println("Item: "+_name);
		System.out.println("Description: "+_description);
		System.out.println("Price: "+_price+" Quantity in stock: "+_quantity+
				" Days until expiration: "+_daysLeft);
	}

}

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

// Stock.java

public class Stock {
	
	// Array that holds all items in the stock
	private Item _item1;
	private Item _item2;
	
	/*
	 * Constructor
	 */
	public Stock() {
		_item1 = null;
		_item2 = null;
	}
	
	// Add item to stock
	public void addItem(Item it) {
		if(_item1 == null) 
			_item1 = it;
		else if(_item2 == null) 
			_item2 = it;
		else
			System.out.println("Stock is full, cannot add "+it.getName());
	}
	
	// Print all items in stock
	public void printStock() {
		if(_item1 != null) _item1.printItem();
		if(_item2 != null) _item2.printItem();
	}
	
	// Calculates the value of all items in stock
	public double sumStock() {
		double sum = 0.0;
		if(_item1 != null)
			sum+=_item1.getPrice()*_item1.getQuantity();
		if(_item2 != null)
			sum+=_item2.getPrice()*_item2.getQuantity();
		return sum;
	}
	
	// Set new quantity for that item
	public void setItemQuantity(String itemName, int newQuantity) {
		if(_item1.getName().equals(itemName))
			_item1.setQuantity(newQuantity);
		else if(_item2.getName().equals(itemName))
			_item2.setQuantity(newQuantity);
		else {
			System.out.println("No such item: "+itemName);
		}
	}
}

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

// Grocery.java

public class Grocery {
	
	public static void main(String[] args) {
		// Build a stock object
		Stock stck = new Stock();
		// Print the stock - it is empty now
		stck.printStock();
		// Add items to stock:
		Item it1 = new Item("Cheese","Smelly green cheese", 1.5, 2, 7);
		Item it2 = new Item("Tomato","Fresh tomamto", 2.6, 25, 10);
		stck.addItem(it1);
		stck.addItem(it2);
		stck.printStock();
		System.out.println("Total price of stock: " + stck.sumStock());
		// Try to insert third item to stock:
		Item it3 = new Item("New item","",0.0,0,0);
		stck.addItem(it3);
		// Change quantity of cheese:
		stck.setItemQuantity("Cheese", 200);
		// Change quantity of item that does not exist:
		stck.setItemQuantity("Bread", 1000);
		System.out.println("Now total price of stock is "+stck.sumStock());
	}

}