Learning Polymorphism and Object Serialization
Pages: 1, 2, 3
There are three classes that implement the Shape interface: the
Rectangle class, the Oval class, and the
Lineclass. You can create you own classes if you feel
like playing with Brainy Draw. As a rule, all classes must override
draw. The classes are explained below.
The Rectangle Class
This class represents a rectangle with two coordinates: (x1, y1), which is the top-left corner of the rectangle; and (x2, y2), which is the bottom-right corner. The class signature and body are given below.
class Rectangle implements Shape {
int x1, y1, x2, y2;
public Rectangle(int x1, int y1, int x2, int y2) {
this.x1 = x1 < x2? x1 : x2;
this.y1 = y1 < y2? y1 : y2;
this.x2 = x1 < x2? x2 : x1;
this.y2 = y1 < y2? y2 : y1;
}
public void draw(Graphics g) {
g.setColor(Color.green);
g.fill3DRect(x1, y1, x2 - x1, y2 - y1, true);
//g.drawRect(x1, y1, x2 - x1, y2 - y1);
}
} // end of class Rectangle
The constructor accepts four arguments, which are the coordinate where the user clicks the mouse (x1, y1) and the coordinate where the user releases the mouse (x2, y2). Because it is not possible to force the user to draw a rectangle by always dragging the mouse rightwards and downwards, x1 and x2 must be swapped if the mouse happens to move to the left. By the same token, y1 and y2 must be swapped if the mouse moves up.
The draw method uses the fill3DRect
method of the Graphics class to draw a rectangle. An
alternative, which is commented out in the code, is to use the
drawRect method. The signature of the drawRect method is
as follows.
public void drawRect(int x, int y, int width, int height)
The signature of the fill3DRect method is as follows.
public void fill3DRect(int x, int y, int width, int height, boolean
raised)
Both methods accepts the following arguments:
x, the abscissa of the top-left corner coordinate of the rectangle,y, the ordinate of the top-left corner coordinate of the rectangle,width, the width of the rectangle,height, the height of the rectangle,raised, in thefill3DRectmethod determines whether the rectangle appears to be raised above the surface or etched into the surface
Knowing both the top-left corner and right-bottom corner coordinates allows us to calculate the width and the height of the rectangle.
Also, to make it more colorful, a green color is used to draw a rectangle, distinguishing it from other types of shapes that use different colors.
The Oval class
TheOval class represents an oval. The class signature and body
are given below.
class Oval implements Shape {
int x1, y1, x2, y2;
public Oval(int x1, int y1, int x2, int y2) {
this.x1 = x1 < x2? x1 : x2;
this.y1 = y1 < y2? y1 : y2;
this.x2 = x1 < x2? x2 : x1;
this.y2 = y1 < y2? y2 : y1;
}
public void draw(Graphics g) {
g.setColor(Color.yellow);
g.fillOval(x1, y1, x2 - x1, y2 - y1);
//g.drawOval(x1, y1, x2 - x1, y2 - y1);
}
} // end of class Oval
Like that of the Rectangle class, the Oval class constructor accepts four arguments, which are the coordinate where the user clicks the mouse, (x1, y1), and the coordinate where the user releases the mouse, (x2, y2). Therefore, the same adjustments must occur if the user drags the mouse upwards or leftwards.
The draw method uses the fillOval method
of the Graphics class to draw an oval. An alternative,
which is commented out in the code, is to use the
drawOval method. The signature of the fillOval method is
as follows.
public abstract void fillOval(int x, int y, int width, int
height)
The signature of the drawOval method is as follows.
public abstract void fillOval(int x, int y, int width, int height)
The arguments for both methods are as follow.
x, the abscissa of the top-left corner coordinate of the oval,y, the ordinate of the top-left corner coordinate of the oval,width, the width of the oval,height, the height of the oval,
In Brainy Draw, an oval is always yellow.
The Line class
The Line class represents a straight line. The line can be drawn if the coordinates of two end points are known. The signature and body of this class are as follow.
class Line implements Shape {
int x1, y1, x2, y2;
public Line(int x1, int y1, int x2, int y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
public void draw(Graphics g) {
g.setColor(Color.red);
g.drawLine(x1, y1, x2, y2);
}
} // end of class Line
Unlike the Rectangle and Oval classes, it
is legal to draw any line with any angle within the coordinate system.
Adjustments in the constructor are therefore not necessary. The draw
method simply calls the drawLine method of the
Graphics class. The drawLine method has the
following signature.
public abstract void drawLine(int x1, int y1,
int x2, int y2)
The method draws a straight line between the points (x1, y1) and (x2, y2).
This method has the following parameters.
- x1, the first point's x coordinate
- y1, the first point's y coordinate,
- x2, the second point's x coordinate,
- y2, the second point's y coordinate.
A line is drawn with the red color.
The drawing board
When drawing graphics on a Java application, one thing is always of
concern. How do you draw on the Frame? In an applet, this is easy
because Applet's paint method is passed a
very useful parameter: an object reference of type
java.awt.Graphics. Once you have the Graphics object,
drawing is easy. The Graphics class has nice methods to draw many
things in a color of your choice. These methods include
drawArc to draw the outline of a circular or elliptical
arc, drawLine to draw a line, drawPolyline
to draw a sequence of connected lines, drawRect to draw a
rectangle, and so on. However, with an application you have a Frame,
and you can't add a Graphics object because it is not a Component. You
would like to somehow initiate the abstract Graphics class and turn it
into a Component. You might think of using a Panel. However, you have
the same problem with a Panel as with a Frame.
There is the Canvas class, which is meant to be extended. What's more, according to Sun's documentation, "A Canvas component represents a blank rectangular area of the screen onto which the application can draw or from which the application can trap input events from the user. An application must subclass the Canvas class in order to get useful functionality such as creating a custom component. The paint method must be overridden in order to perform custom graphics on the canvas."
Sounds perfect. A Canvas even has the paint method that can perform custom graphics on its body. But, it said that a Canvas is a blank rectangular area. This has profound implication if it is to be used to draw shapes in our Brainy Draw. Everything would take a rectangular space, including an oval and a line. When you draw an oval on top of a rectangle, the corners of the oval will occupy some space. Also, if you draw a line, the space taken is not only for the dots that compose the line, but a rectangular area. And a Canvas is not hollow, so it always covers a rectangular space even though you are drawing an unfilled rectangle.
Figure 2 shows the unexpected side effects. The oval and the rectangle don't physically touch each other. However, the top-right corner of the rectangle is invisible because the rectangular area of the oval occupies it.
|
I decided to use what had been considered the ideal candidate for
the drawing board in the first place: an applet. Though it doesn't
sound conventional, using an applet with a Java application is not
forbidden and does not necessarily turn your application into a Web
browser. As you can see later, it works as expected. The coding part
is also not hard. You just need a class that extends
java.applet.Applet to utilize its paint method. Once you
have a Graphics object that comes free with the paint
method, you can use its methods to do a lot of drawing. Now, instead
of a drawing panel that is an object of Panel, you have a panel that's
a subclass of Applet. Adding the applet to the Frame is also very
simple using the add(Component c) method because
Applet is derived from Component.
The signature and body of the PanelApplet class, the
new class that you use as a drawing board, is given below.
import java.applet.Applet;
import java.util.*;
class PanelApplet extends Applet {
Vector shapes = new Vector();
public void paint(Graphics g) {
Enumeration e = shapes.elements();
while (e.hasMoreElements()) {
Shape s = (Shape) e.nextElement();
s.draw(g);
}
}
} // end of PanelApplet class
Vector shapes is a container for all the objects drawn
(rectangles, ovals, and lines). An element will be added when the user
draws a shape and removed when the user clicks Undo. To display these
objects correctly on the screen, you need to tell the paint method to
loop through the Vector and draws the elements in the correct
order.
The most interesting part of the overridden paint method are the following lines.
Shape s = (Shape) e.nextElement();
s.draw(g);
Each element (which is a Rectangle object, an
Oval object, or a Line object) is upcast to
a Shape object; and without having to know whether the
Shape object is actually a Rectangle, an
Oval, or a Line object, you can call the
draw method. All because, and this is the magic of
polymorphism, the JVM knows which draw method to call. If
the Shape object is a Rectangle, the
draw method in the Rectangle class is
called. If it is a Line object, Java will invoke the
draw method in the Line class. As a result,
each object will be correctly drawn by the paint
method.
