16 November 2000 Simon Gronlund is earning his Master of Science in Computer Science at the Royal Institute of Technology, Sweden, as an adult student. He also teaches Java and computer-related courses at the college. When he isn't tampering with his Warp 4 PC, he spends his spare time with his two boys and his wife. If you have a comment about the content of this article, please feel free to vent in the OS/2 eZine discussion forums |
|
Into Java, Part 11
Quite a few times we have used some
different Object Oriented Programming (OOP) techniques and I have not made a fuss
about that. Now I will use a few minutes to explain two concepts used. The first
one is, how can you make yourself a factory that creates objects that you need?
And is that important to know? The second topic will be, what is a method overriding
another one? What are the pitfalls, and what are the benefits? |
In Sweden we use a person's social
security number (it is much more used than it need to be for integrity reasons)
to find out a person's sex, and hence we can send that number as a parameter to
Human and we will receive an object of the correct sex. If we can provide a method
that takes the social security number as parameter and that will return a subclass
to Human, a Woman or
a Man depending
on the number:
public Human getPerson(long ssNumber)This technique does not seem to be such a killer as it indeed is. The Factory pattern may be the perfect solution, not only to humans but to many other situations. Perhaps you would like to get something called PrinterObject, having a few methods taking print jobs. But depending on what printer is plugged to the computer or the network, different objects shall be returned to you, but with common interfaces; your factory in work.
super.paintComponent(g);and we said that it is a good practice to always do that call. Why? Since the class we worked with inherited from JPanel, that in fact inherited from JComponent that has a method with the same name. So, two generations down we are overriding the method paintComponent with our own method and implementation. If we are not doing that super.paintComponent(g) the "grandclass" will never receive any calls and hence can not update the stuff it is responsible for. A veeery common mistake.
But why not simply name our own method
differently? Since the system always looks for methods with certain names, in this
case paintComponent,
but there are several other examples. Anyway, if you override a method of the
system, make sure you always do that call to
super, the ancestor of your
class.
On the other hand, when creating
your own hierarchy of classes you will be the judge if you have to or not have to
call any ancestor class when overriding methods. Think of the class Human again,
used in a company it is for pay-rolls and have a method
raiseSalary(float percentRaise).
This method is useful for many employees as you inherit Human to Receptionist, Clerk,
Programmer etc. But Developer is treated specially, a raise is not only a percentage
raise but a minimum of $100 as well and the raise is these $100 plus the percentage
raise computed on that temporary amount. Hence we must first add 100 to the old
salary and after that we call super
We overrode the method raiseSalary, but made a call to super as well. And different from constructors you do not need have this call as the first line (within constructors calls to super have to be the first line, when such calls are used). public void raiseSalary(float percentRaise) { float temp = getSalary(); setSalary(100 + temp); super.raiseSalary(percentRaise); }
But how to do with the Boss, that
also inherits from Human? A Boss never get a percentage salary raise, no it goes
upwards step by step. Hence we may override the raiseSalary
method but we do not do
a call to super. Boss simply implements the method as
Next time we will look into the OOP concept of overloading methods and polymorphism, as the partner to override a method. public void raiseSalary(float raise) { float oldSal = getSalary(); setSalary(oldSal + raise); }
But to my surprise i found that I
had introduced another bug I did not intend to. I beg your pardon. With the old
version of PaintBox strange things happened to the shapes if the window was repainted,
as from resizing it or getting focus back from being hidden. The shapes almost disappeared,
or they really did. Why so? Because I did not think of that situation when modeling
the method! What was the problem? In the paintComponent
method I used the stopX and stopY variables
to be the width and height. That worked as long as no more call was made to this
method. But repainting a component will give another call. Any new call to this
method will again compute the width and height, but this time with trashed values
in stopX and stopY.
The solution is to use temporary
variables for the width and height, and have them recomputed every time a call is
made to the paintComponent method. And of course never ruin neither the start point
or the stop point. Thus we introduce startXX and startYY as temporary starting values, since we sometimes
must switch them with the upper left point as seen below. Further we introduce height and width. The
solution is told in the image below.
|
Is it then possible to outline the
ovals while being painted? Yes, of course. Simply declare a new class variable private boolean mousePressed among
the other declared class variables. Within the MouseAdapter : mousePressed
method within the class'
constructor, add the sole line line mousePressed = true. And add its counterpart
mousePressed = false within
the mouseReleased method,
preferably as the first line of that method. This boolean variable; and the toggling
lines, will be used by the paintComponent method.
To paintComponent
we will add these lines
if (mousePressed) {
g.drawRect(startXX, startYY, width, height);
}
first in the block handling the oval
painting, after the "// remaining is oval" comment. Hey presto, it works.
But why this extra variable? Else the sole line drawRect ...
seen above will not vanish
after you have completed your task. But using this variable, it is set to false
when the mouse button is released, and the outline is not painted. This single boolean
variable simply keeps track of whether a shape is being painted or not, and when
the shape shall be considered finished the outline is not painted.
You may easily crouch down under
the overwhelming number of booleans used in bigger applications. And under the careful
planning needed to be sure how they will interact and how they must not restrain
legal actions.
|
But how will it ever be painted?
Please copy the entire paintComponent from the PaintPanel
class and paste it into
this class. Then remove the call to super. Why? Since this class is no subclass
to anything (except Object as any class is), it will simply paint itself, a shape.
Further remove these newly added lines on if mousePressed
from it.
Finally, change the tests to look
like
if (shape == PaintPanel.LINE) { // that is line
and
if (shape == PaintPanel.RECTANGLE) { // that is rectangle
The class is now complete.
private Vector storedShapes;
storedShapes = new Vector();
Then continue within the MouseAdapter : mouseReleased method and add some lines
PaintShape tempShape = new PaintShape(filledShape, paintColor, shape, startX, startY);
tempShape.setStop(stopX, stopY);
storedShapes.addElement(tempShape);
at the end, only the repaint()
call will remain the last
line. When a user releases his mouse button after painted a shape all these values
are known to the application, hence this object can be instantiated. But this object
is not instantiated during the painting phase, the values are only used internally
within the PaintPanel object until the mouse is released. Next step is to add the tempShape into
the store (note that from Java 1.2 you may use add(Object o)).
So far we have a way to make ourselves
shapes and add them to the store, but still we cannot retrieve them. They vanish
when we begin to paint another shape though they really are stored in our vector.
Hence we must change the recently changed paintComponent
of the
PaintPanel class. Each time
this method is called, the panel is repainted and that erases everything else upon
the canvas. Thus we must force this method to also paint the objects stored in the storedShapes object.
Simply add these lines at the second first line of the
PaintPanel's paintComponent method,
only the call to super shall
come before these lines
for(int i = 0; i < storedShapes.size(); i++) {
PaintShape tempObj = (PaintShape) storedShapes.elementAt(i);
tempObj.paintComponent(g);
}
Compile and
go! The for loop will run as many turns as there are shapes added to the vector,
the size of the vector reflects the number of objects stored. We start with element
zero (why zero has a historic reason, based on the fact that the first element has
zero offset from the starting point). Hence, if there are five
objects stored in a vector, the last element is located at index four, as object
one is located at index zero. Thus you may always ask for the size of the vector
and run the loop as long as i is less than the size. If there is no element added
yet, no turn within the loop is allowed, the test is always done before you enter
the loop.
A vector always stores Java Objects,
any kind is possible (except for data types since these are no
Objects but data types),
and thus we need to cast the elements to the proper type. This time we know, since
we designed the code ourselves, that they are of PaintShape
type (later on we will look
into how to do when we are not sure). This cast is one when we merely tell the compiler
what type it is to expect and to use.
The retrieved object is stored in
a temporary local variable. Finally the object's paintComponent
method is called, using
the parameter g as
we are used to. But note, this time we could have given the painting method any
name since it will never get a call from the system. As long as we are making the
calls ourselves, we may name the methods any name. But the system uses specified
names we have to stick to.
|
We have acquainted ourselves with java.util.Vector,
a kind of store, or better word for it is a Data Structure. We used two of its many
methods, public void addElement(Object obj) and public Object
elementAt(int index). Used
a for loop to get objects out of the vector, casting and using them. Next time we
will look into how a vector looks like under the hood, as well as another similar
data structure.
But we did not implement a good way
to erase shapes from the canvas. How can we do that? How to make it possible to
click any shape and have it removed? The next time we will add that feature to the
application and then we have to discuss some other properties of Java. We will also
discuss the OOP topic of overloading, not that new to us but yet unexplained.
|
|
|