תכנות מתקדם ב-Java/בנאים/תרגילים

הקשקשן עריכה

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

המצולעים עריכה

את המצולעים שנצייר נייצג בעזרת המחלקה Shape

כל מצולע שנרצה להציג יכיל (לכל הפחות) את הנתונים הבאים:

  • מיקומי הקדקודים של המצולע. חישבו על החלון בו נציג את הצורות כמערכת צירים שמתחילה ב-(0,0) ומסתיימת ב-(640,480). את המיקומים של הקדקודים יכילו שני מערכים מטיפוס int, כאשר כל מיקום מורכב מזוג משתנים - מערך אחד יכיל מיקום אנכי והשני יכיל מיקום אפקי. כך, לדוגמה, אם נרצה שהקדקוד הראשון של המצולע יהיה במיקום (100, 100), נכניס לאיבר הראשון במערך הראשון ולאיבר השני במערך השני את הערך 100.
  • צבע המצולע. לשם ההצגה נשתמש באובייקט מסוג Color. בשביל שנוכל להשתמש בו, נוסיף את הפקודה הבאה בתחילת המחלקה: import java.awt.Color;
  • מספר הקדקודים במצולע.

אם כך, שדות אפשריים עבור המחלקה הם:

// Arrays holding vertices locations
private int[] _xLocs;
private int[] _yLocs;
// Number of vertices
private int _vertices;
// Shape color
private Color _col;

ניתן יהיה לאתחל מצולע בכמה צורות:

/**
 * Constructor. Create the shape with the default color
 * @param xLocs X locations
 * @param yLocs Y locations
 */
public Shape(int[] xLocs, int[] yLocs)

/**
 * Constructor
 * @param xLocs X locations
 * @param yLocs Y locations
 * @param color Shape color
 */
public Shape(int[] xLocs, int[] yLocs, Color color)

/**
 * Copy constructor
 * @param other Other shape to copy
 */
public Shape(Shape other)

בנוסף לבנאים, המחלקה Shape צריכה לממש כמה שיטות פשוטות נוספות:

/**
 * @return Shape color
 */
public Color getColor()

/**
 * @return X locations
 */
public int[] getXLocations()

/**
 * @return Y locations
 */	
public int[] getYLocations()

/**
 * @return Number of vertices
 */
public int getVertices()

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

לוח השרטוט עריכה

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

import java.awt.*;
import javax.swing.*;

@SuppressWarnings("serial")
public class Animator extends JFrame {

	// Prefered window size
	private static final int HEIGHT = 640;
	private static final int WIDTH = 480;
	// Shapes to draw
	private Shape[] _shapes;

	/**
	 * Constructor. Creates the window.
	 * @param shapes Shapes to draw
	 */
	public Animator(Shape[] shapes) {
		super("Animator");
		_shapes = shapes;
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBackground(Color.WHITE);
		setPreferredSize(new Dimension(HEIGHT, WIDTH));
		pack();
		setResizable(false);
		setVisible(true);
	}

	/**
	 * Draws the shapes. Ignores null shapes.
	 */
	public void paint(Graphics g) {
		for(int i=0; i<_shapes.length; i++) {
			if(_shapes[i] != null) {
				g.setColor(_shapes[i].getColor());
				g.drawPolygon(
						_shapes[i].getXLocations(), 
						_shapes[i].getYLocations(),
						_shapes[i].getVertices());
			}
		}
	}

}

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

שיטות נוספות עריכה

שיטות נוספות שנרצה לממש הן שיטות שיאפשרו שינויים פשוטים בצורות שיצרנו:

/**
 * Moves the shape
 * @param dx horizontal alignment
 * @param dy vertical alignment
 */
public void move(int dx, int dy)

/**
 * Sets the shape color
 * @param c new color for that shape
 */
public void setColor(Color c)

שיטות אלה יאפשרו לנו להזיז צורה ולשנות את צבעה. הערה: שיטת ההזזה צריכה לקבל שני מספרים שלמים (יכולים להיות שליליים), ולהזיז את הצורה על פיהם. למשל, אם השיטה מקבלת את הארגומנטים 5 ו-10, כל ערכי ה-X צריכים לגדול ב-5, וכל ערכי ה-Y - ב-10.

מימוש עריכה

משימתנו, אם כך, היא:

  1. ליצור את המחלקה Shape, כפי שתוארה בתחילת הקטע, ובה יהיו:
    • משתנים שיחזיקו את הנתונים הדרושים. ניתן להשתמש במשתנים שפורטו בתחילת הקטע, אך זה לא הכרחי: כל עוד השיטות הציבוריות יחזירו את הערכים הרצויים - לא משנה באיזה דרך בחרתם להחזיק את הנתונים (כמובן, כל עוד היא לא בלתי-יעילה לחלוטין).
    • בנאים - כל שלושת הבנאים שפורטו. הערה: ערך ברירת המחדל של הצבע הוא שחור, שמיוצג באמצעות Color.BLACK. אל תשכחו לייבא את המחלקה המתאימה באמצעות השורה import java.awt.Color; בתחילת המחלקה Shape.
    • כל השיטות הציבוריות שפורטו.
  2. ליצור את המחלקה ShapeDriver, שתכיל את ה-main של התוכנית.
    • המחלקה תיצור כמה צורות, תכניס אותן למערך, ואז, בהנחה ששם המערך הוא myShapes, תיצור מופע של אובייקט מסוג Animator (המחלקה הגרפית) באמצעות השורה new Animator(myShapes);.
  3. בעת הכתיבה, נסו להקפיד על כמה דברים:
    • תיעוד נכון - השתמשו ב-Javadoc.
    • ממשו את כל השיטות הציבוריות הדרושות. בשעת הצורך השתמשו בשיטות ובמשתנים פרטיים.
    • במימוש הבנאים, אל תשכפלו קוד. בשעת הצורך, פנו מבנאי לבנאי. זכרו את הכללים הנוגעים לכך שפורטו בפרק.
    • אל תתעלמו ממקרי הקצה; אם משתמש ינסה ליצור צורה לא הגיונית (לדוגמה - ייתן מערכים שהם Null, מערכים שלא תואמים באורכם עבור ערכי ה-X וה-Y, או כל מקרה חריג אחר) - הדפיסו הודעה מתאימה וצאו מהתוכנית.

המחלקה Shape עריכה

פתרון
import java.awt.Color;

/**
 * Class: Shape.java
 * 
 * Represents a single colored polygon
 */
public class Shape {

	//	Arrays holding vertices locations
	private int[] _xLocs;
	private int[] _yLocs;
	//	Number of vertices
	private int _vertices;
	//	Shape color
	private Color _col;

	// Default color
	private static final Color DEFAULT_COLOR = Color.BLACK;

	/**
	 * Constructor. Create the shape with the default color
	 * @param xLocs X locations
	 * @param yLocs Y locations
	 */
	public Shape(int[] xLocs, int[] yLocs) {
		this(xLocs, yLocs, DEFAULT_COLOR);
	}

	/**
	 * Constructor
	 * @param xLocs X locations
	 * @param yLocs Y locations
	 * @param color Shape color
	 */
	public Shape(int[] xLocs, int[] yLocs, Color color) {
		if(xLocs == null || yLocs == null) 
			reportError("Null array");
		if(xLocs.length != yLocs.length) 
			reportError("Non-matching X and Y arrays");
		_vertices = xLocs.length;
		_xLocs = new int[_vertices];
		_yLocs = new int[_vertices];
		for(int i=0; i<_vertices; i++) {
			_xLocs[i] = xLocs[i];
			_yLocs[i] = yLocs[i];
		}
		_col = color;
	}

	/**
	 * Copy constructor
	 * @param other Other shape to copy
	 */
	public Shape(Shape other) {
		this(other._xLocs, other._yLocs, other._col);
	}

	/**
	 * @return Shape color
	 */
	public Color getColor() {
		return _col;
	}

	// Clone a given integer array
	private static int[] cloneArr(int[] arr) {
		int[] clone = new int[arr.length];
		for(int i=0; i<arr.length; i++) {
			clone[i] = arr[i];
		}
		return clone;
	}
	
	/**
	 * @return X locations
	 */
	public int[] getXLocations() {
		int[] temp = cloneArr(_xLocs);
		return temp;
	}

	/**
	 * @return Y locations
	 */	
	public int[] getYLocations() {
		int[] temp = cloneArr(_yLocs);
		return temp;
	}

	/**
	 * @return Number of vertices
	 */
	public int getVertices() {
		return _vertices;
	}

	/**
	 * Moves the shape
	 * @param dx horizontal alignment
	 * @param dy vertical alignment
	 */
	public void move(int dx, int dy) {
		for(int i=0; i<_vertices; i++) {
			_xLocs[i]+=dx;
			_yLocs[i]+=dy;
		}
	}

	/**
	 * Sets the shape color
	 * @param c new color for that shape
	 */
	public void setColor(Color c) {
		_col = c;
	}

	// Report critical error and quit
	private void reportError(String err) {
		System.err.println("Critical error: "+err);
		System.exit(1);
	}

}


המנוע עריכה

פתרון
import java.awt.Color;

/**
 * Simple driver for the animator program
 * 
 */
public class ShapeDriver {

	public static void main(String[] args) {
		int[] xLocs1 = {100, 200, 200, 100};
		int[] yLocs1 = {100, 100, 200, 200};
		int[] xLocs2 = {50, 0, 100};
		int[] yLocs2 = {50, 100, 100};
		Shape s1, s2, s3;
		s1 = new Shape(xLocs1, yLocs1, Color.BLUE);
		s2 = new Shape(s1);
		s2.move(100, 100);
		s2.setColor(Color.GREEN);
		s3 = new Shape(xLocs2, yLocs2);
		Shape[] myShapes = {s1, s2, s3};
		new Animator(myShapes);
	}

}