|
|
Laboratory 4
Being Classy
Week of 14 February, 2005
|
Downloads
SmileWindow.java and SmileyFace.java.
Objective
Objects are the basic units of programming in object-oriented languages. In
Java, new types of objects are defined by designing and implementing classes. A
class is the blueprint from which objects can be created. This laboratory
introduces designing and working with your own classes. In particular, the
laboratory has you build a program by creating two classes. The laboratory
also considers the different kinds of methods that classes can have.
Key Concepts
- Class design, data abstraction, and information hiding
- Instance variables
- Accessor, mutator, and facilitator methods
- Constructors
- Graphics.drawOval() and Graphics.fillOval() methods
Getting started
Copy the laboratory files to your PC.
Design
- For this lab, we are going to design and create a class SmileWindow and a
helping class SmileyFace. Together, the two classes enable a program to display
a "smiley face" in a window, as in the figure below.
- For example, the following method main() will display a smiley face in a window.
This is the main() method in the SmileWindow.java file.
However, it does work yet (nor compile yet), but it will by the end of the
lab.
public static void main(String[] args) {
System.out.print("Window size for smiley (pixels): ");
Scanner stdin = new Scanner (System.in);
int size = stdin.nextInt();
SmileWindow myWindow = new SmileWindow();
myWindow.setSize(size);
myWindow.makeVisible();
}
- For SmileWindow, the size of the smiley face is dependent upon the size of the
JFrame containing the smile. That is, the the object displaying the face will
automatically scale and position the smiley face to fit properly in whatever
size window is requested.
- The first step in creating a program to satisfy the specified requirements is
the design phase. We need to develop the overall framework for the program. We
must think about how the user will interact with the program.
- The user should be able to enter the size of a window and have a window of that
size appear with a smiley face in the window.
- The user is not expected to provide the features of the face or the positions of
these features.
- The rendering (i.e., drawing and painting) will be produced automatically by SmileWindow and a helping class SmileyFace.
- A SmileWindow user does not need to have any understanding of how the smiley
face is drawn or scaled in its window. Instead, a SmileWindow user should have
access to a SmileWindow constructor that sets up a new SmileWindow object and
SmileWindow methods that provide access to existing SmileWindow objects.
- In order to preserve encapsulation, a SmileWindow user should not be able to
access or change directly any of the instance variables of SmileWindow. The
SmileWindow user should be constrained to use the constructors and methods when
manipulating a SmileWindow object.
- Therefore, the SmileWindow instance variables should be private and the
constructors, accessors, and mutators should be public.
- Although a SmileWindow user does not need to have any understanding of how the
smiley face is drawn or scaled in its window, we – as the implementors of
SmileWindow – must have that understanding.
- You may have wondered why we are going to implement two classes for our task.
Yes, it is possible to create one class that does it all. However, in
object-oriented programming, it is best to break tasks into easily managed
pieces. Let's begin by creating a class SmileyFace that will do the actual
rendering of the face. Our other class SmileWindow will make use of SmileyFace
to produce a window with a smiley face.
- By separating these two aspects of the problem, it makes it easier to make
enhancements and reuse the classes for other purposes. For example, perhaps in a
future program, we will want a button with a smiley face on it. We can create a
button and then access the SmileyFace class to draw the face, instead of
creating a completely new class from scratch.
More Design and Code Production: SmileyFace.java
- Let’s begin building the SmileyFace class. A SmileyFace object will compute the
location of the eyes and smile from the diameter and the position of the face.
Therefore, a SmileyFace needs to maintain only the diameter of the face and the
location of the face.
- Open the file SmileyFace.java.
import java.awt.*;
public class SmileyFace {
private int diameter;
private int x;
private int y;
// accessor methods . . .
// mutator methods . . .
// constructors . . .
// facilitator methods . . .
}
- The SmileyFace class contains three instance variables: diameter, x, and y.
Following normal information hiding practices, the instance variables are
declared private.
- It is good programming practice to create accessor and mutator methods for
instance variables. An accessor method typically begins with get followed by the
variable name. Its return type is the same as the variable type. Add accessor
methods to your program for each instance variable. We have already supplied the
first one for you:
public int getDiameter() {
return diameter;
}
- Mutator methods are methods that change the value of the variable. Mutator
methods typically begin with set followed by the variable name. Mutator methods
are passed a parameter of the same type as the variable. Mutator methods do not
return a value, so their return type is void. Here is a mutator method for the
diameter.
public void setDiameter(int d) {
diameter = d;
}
- Beginning programmers are sometimes tempted to name a formal parameter with the
same name as an instance variable that they want to update. This practice does
not produce the desired result. Suppose we had written our setDiamet mutator in
the following way.
public void setDiameter(int diameter) {
diameter = diameter;
}
- When the method is invoked, the name diameter refers to a formal parameter.
Therefore, the assignment assigns the formal parameter its own value. It does
not change the instance variable with that same name.
- Add mutator methods to your program for each instance variable.
- Although providing these methods may seem to be unnecessary, the methods are
important for achieving information hiding, an important software engineering
principle.
- Properly hiding information gives the program, not the user, control over the
circumstances under which variables can be accessed or modified. You can also
choose to modify the internal representation of the smiley face, but the user
will not know the difference because the user will still call the same accessors
and mutators to set the diameter and coordinates of the face. Separating the
internal representation from the user interface, including the accessors and
mutators, is another important software engineering principle.
- Compile your program. Although there is nothing to run right now, compiling your
program helps find syntax errors early. After every code revision, it is a good
idea to compile the code to check for and to correct syntax errors, such as a
missed semicolon or a misspelled variable name.
- Now we will add two constructors to the code. In particular, add a constructor
that initializes the diameter, x and y variables with user-supplied values. To
preserve information hiding, make sure that your constructor calls the
appropriate mutator methods rather than accessing the variables directly. We
provide the outline for another constructor:
public SmileyFace(int d, int xValue, int yValue) {
// configure a smiley face with the specified characteristics
}
- Do we need any other constructors? We may need to create a SmileyFace object
before we know the diameter and position of the object. In this case, we need a
default constructor that has no parameters and simply initializes the object.
Have the default constructor represent a smiley face with diameter 2 at position
(0, 0).
public SmileyFace() {
// configure a smiley face with the default characteristics
}
- Now that we have created our accessor, mutator and constructor methods, we can
concentrate on the facilitator methods. Facilitators are methods that help the
object perform its intended task. In this case, we need to provide the means for
a SmileyFace object to draw itself. The paint() method must take into account
all of the current attributes of its SmileyFace object in rendering the face.
- Let's now learn how to draw ovals. Open up the
Java API specification and find the method details for
Graphics.fillOval() and Graphics.drawOval() located in the java.awt package.
- Review the SmileyFace methods drawFace(), drawEyes() and
drawMouth(). Do you
understand how the methods work? It may help to work through the code with
pencil and paper.
- You will notice that instead of calculating the facial feature coordinates and
drawing them in a single method, we distributed the tasks across multiple
methods. In programming, it is generally recommended that you break down a task
into easily manageable pieces. We could have placed all the code to draw the
face in the paint() method. But, it is easier to read and debug code that is
broken down into manageable pieces, each represented by a separate method. Add
the following lines to your object's paint() method:
drawFace(g);
drawEyes(g);
drawSmile(g);
- Compile your SmileyFace class. If you get any compiler errors, try to determine
where you made any mistakes. If you cannot determine what is wrong, seek help
from an instructor.
Putting it Together: SmileWindow.java
- Open the file SmileWindow.java. As written, class SmileWindow defines a
blueprint for square windows. The class defines a default constructor that
creates a JFrame with the title “smile”. It also provides a mutator method
setSize() that resizes the window and a facilitator makeVisible() method that
makes the window visible.
- Compile and run the program. It should prompt you for a size and then display an
empty window with width and height equal to the size which you provided as
input. Do you understand how the program works?
- Now we will add our SmileyFace to the window. Uncomment the private instance
variable named face of type SmileyFace in your program.
private SmileyFace face;
- When we call our SmileWindow constructor, it initializes the JFrame for our
window and sets its default close operation to EXIT_ON_CLOSE. Add a line of code
to beginning of the constructor to initialize the face instance variable by
calling its default constructor.
face = new SmileyFace();
- When the window size is set in the setSize() method, we need to set the size and
position of our SmileyFace object. Add code to setSize() to perform the
following:
- Set the x value of the SmileyFace object to 1/8 of the window dimension.
- Set the y value of the SmileyFace object to 1/8 of the window dimension.
- Set the diameter of the SmileyFace object to 3/4 of the window dimension
- Now our final step is to call SmileyFace.paint(). In the makeVisible() method,
add the following lines of code:
Graphics g = window.getGraphics();
face.paint(g);
This code must be added BELOW the window.setVisible(true); line, or
else Java will run into an error.
- We want face to draw itself in window, so we call window.getGraphics() to get
the graphics context for window. We pass this graphics context to face.
- Compile and run the program. If it does not compile or run correctly, try to
determine what the problem is. If you cannot figure it out, ask your lab
instructor for assistance.
- When your program runs successfully, try entering different sizes to see if your
face is drawn correctly. It is possible to enter sizes that are too small or
sizes that are too large. In later chapters we will learn how to address this
problem.
- Note that when you are finished running the program, you will have to
close the windows by hitting the "X" button in the upper-right of the
window. Also, the SmileyFace is drawn only once - which means that if
a window is covering part of the face, when you move the window, the part of
the face that was covered is not re-drawn.
Finishing up
- Copy any files you wish to keep.
-
Submit your files SmileWindow.java and SmileyFace.java.
- Delete any laboratory files that you copied or created.