Here is a simple window with a disk in the middle:
Modify the Disk class so that every time it paint()s itself, it gets one pixel bigger in all directions:
Does this work? Run it to find out.
Ahh. Trying that teaches us that the fillOval() doesn't represent the disk as a circle in terms of its center (x,y) and radius but as a rectangle with (x,y) as the upper left corner. So we have to do something like this. If the audience for this code consists of relative Java novices, then we should probably document this violation of expectations with a comment.
Of course, unless we want every Disk object in every program to behave like this, then we will want to move this behavior into a new class, perhaps a subclass of Disk named GrowingDisk. (Try it at home!)
Last time, we studied BallWorld as a way to learn about two OO ideas in detail, constructors and inheritance. A constructor is the mechanism by which an object is created and initialized. In Java, a constructor looks like a method, but is different from other methods in several ways, including that it can be invoked only by use of the operator new.
Inheritance is a programming technique that allows us to create a new kind of object that extends the behavior of an existing kind of object. The new kind of object possesses all the instance variables and methods of its parent, plus any new instance variables and methods defined in the new class.
While discussing both of these ideas, we encountered the special variable super, which allows an object to access the parts it inherits from its superclass. There is a corresponding special variable this for accessing parts of the current class, when necessary.
We also encountered a new access modifier, protected, which gives the programmer a way to make parts of a class accessible to its subclass. Java allows us to declare both instance variables and methods as protected, though OO principles encourage us to use it only for methods.
Notice that our Disk class does not have an extends clause. Even so, it inherits from an existing class, Object. This root of all classes provides basic behavior that every object in a Java program needs to have. We have seen two of these behaviors already, toString() and equals(). You have written a method that replaces the inherited behavior for toString() in one of your classes, and in a few weeks we will learn why and how to "override" the inherited behavior for equals().
The code necessary to do even simple graphics of this sort in a modern operating systems with a modern graphical user interface is quite complex and long. Java provide a multitude of graphics classes as a part of its standard library, which we then reuse by inheritance and instantiation. As a result, we don't have to concern ourselves with system-level details and can focus on the features of our application. I often wish some of my other favorite languages did the same!
This semester, we we will Java's Abstract Windowing Toolkit (AWT) to do most of our graphics. The AWT provides a number of useful classes and interfaces that allow us to create simple graphical programs without having to deal with the GUI in our operating systems.
Side note on Java graphics: Java's standard libraries include two graphics graphics packages: the AWT and Swing. AWT came first, and Swing later. If you read about Java graphics or study other programs, you may encounter Swing classes, such as JFrame. There are several differences in the libraries, and thus differences in the programs we write to use them. We use the AWT in this course because it is simpler, though sometimes less convenient. We will rarely write programs in class that require Swing's flexibility.
The AWT actually does more than provide us classes. It provides some classes that define the structure of entire programs and hooks for application-specific code. Such a set of classes is called a framework. A framework provides the overall control structure for an application. This saves us application developers from having to rewrite the control structure with each new program.
We will study this in greater detail after midterm. For now, the primary effect on us is that we will not see much of the code that is being executed when we run our programs! We have already seen examples of this in BallWorld, which uses a number of methods from the Frame class, including methods that control the creating and redrawing of our windows.
All we had to do to create a BallWorld was to define the appearance of the window, via the method paint(). This method received a Graphics object as an argument.
Notice that we never create a Graphics object ourselves. The Java run-time system hands our frame a Graphics object each time it asks the frame to paint() itself. So far, we have seen two of the messages to which Graphics objects respond:
But they respond to many useful messages, including:
Here is a simple demo frame for these basic messages. If you are curious, you can read more about the Graphics class and its interface in the javadoc for the Graphics class.
Our worlds are a bit uninteresting, as the Balls fly out of the frame pretty quickly. Shouldn't the balls bounce off the walls?
Only if we tell them how to!
Notice I how phrased that: Only if we tell them how to! Balls themselves should know how to bounce.
Why not have the Frame detect collisions and tell the Balls to change direction? To do so, the Frame would have to...
This requires the Frame to know a lot about the Ball's implementation. If we decide to change the Ball's implementation later, we will have to change the corresponding code in Frame. But if the Ball class reveals its implementation to the Frame class, it reveals it to all objects. So we may have to change a lot of other classes, too.
This brings to mind an OO design principle we've seen repeatedly and should follow here:
Any code that manipulates an object's instance variables should be in the object's own class.
The Ball class should be responsible for all operations on its instance variables. So Balls should take care of detecting collisions and changing directions.
With that settled, we encounter a more mundane concern. How can a Ball change its direction? If it multiplies its delta value by -1. Try it out -- this simulates a reflection just fine.
Then we encounter a more serious issue. In order to know if it has gone out of bounds, a Ball must know the width and height of the Frame in which it resides. But the Ball class does not keep track of this information. That's not a major problem, because we can create a new kind of Ball that does!
Quick Exercise Why not add this behavior directly to the Ball class? When might we want to?
Take a look at my solution. Notice:
Those protected access methods reinforce the Taking Care of Business principle we saw again just above: Every class should be responsible for its own stuff. That includes superclasses.
Someone in class suggested a nice improvement: "The Ball should detect the collision at radius pixels from the boundary." Actually, it should detect right and bottom collisions at 2*radius pixels from the edge (why?), and left and top collisions right at the boundary (why?). This makes a nice practice exercise at home!
A few things... First, a simple coding practice. Whenever you write:
if (someExpression) return true; else return false;
You can write the simpler:
This is simpler to read. Also, any code you don't write can't cause a bug!
Second, a constructor should not, in general, do a bunch of stuff. It should only initialize the state of the object. Other methods provide the object's behavior...
Third, several of your solutions had execute() depend on the fact that rangeOfBids() having already run, or vice versa. We usually can't depend on the order in which messages are sent. That is up to the client...
The last two of these indicate a misconception about what our classes define. Remember to keep separate the object and the applications that use it...
Figure out what the code does, and how. It has tests that illustrate several common use cases.
How could we improve this code? After only six weeks studying OO design and programming, you already know several principles that would help us make this code easier to read, understand, modify, and maintain.
Bring a list of suggested improvements to the code when you come to class next time.