Download the Calculator project files.
This assignment emphasizes the following topics:
This assignment is due by midnight on Thursday, October 6, 2005. You should send electronic versions to sd-psets@lists.olin.edu, and paper versions to Katie.
Specifically, you will implement an active object class which will interact with a Calculator object to respond to the user. The Calculator can do such things as figuring out which button the user presses, and managing redrawing, displaying, and other various things that happen on your computer screen. However, it does not actually know how to interpret these button presses, or what answer to display. That is your object's job.
Your object will be active in the sense that it has its own Thread. You will need to write a constructor for your object that takes an object that implements the Calculator interface, and creates and starts a new Thread. For the run() method of the thread, you will wait for button presses, and tell the Calculator exactly what to display after each button press.
Find a 4-function (or better, but not reverse Polish) calculator. (There is one running under Windows: Accessories>Calculator) Run a few experiments on it. What does it do under various key sequences. Try to think of slightly non-intuitive sequences. E.g., what does it do if you press digit op op digit equals? Run at least four experiments and record their results. If you find it helpful, you may want to record your results in a table of current state vs. next button press.
Calculator.OP_ADD, | Calculator.OP_SUB, | Calculator.OP_MUL, |
Calculator.OP_DIV, | Calculator.EQUALS, | Calculator.DOT, |
Calculator.CLEAR, | Calculator.BACKSPACE, | Calculator.MEMORY. |
These are all declared as static and final. This means that they are accessible as parts of the Calculator interface, and that their value cannot be changed.
The values of all of these constants are distinct from one another and from the numbers 0 through 9; it is intended that the numbers 0 through 9 serve as buttonIDs for their respective buttons.
An additional constant, Calculator.NO_OP, is not a buttonID but is provided for convenience.
Imagine that foo is an int representing one of the buttonIDs. Write a boolean expression that will be true exactly when foo is one of the digits 0 through 9. Write another expression whose value is true exactly when foo isn't one of the numbers.
Write a (private) helper method (for possible inclusion in your button handler logic) that accepts as its single argument an int representing one of the button IDs and returns the String representing the label of that button. Challenge: Can you write the method body in a single line of code? Be sure to write the complete method, and give it an appropriate name.
public int getNextButton();The int returned is a buttonID indicating which of the buttons was pressed: one of 0 through 9, or one of the buttonIDs defined in the Calculator interface. Each time that the getNextButton method is called, it returns the next button pressed. So, if the calculator's user presses 3 + 4 =, your object's first call to your Calculator's getNextButton() method will return 3. The next call to getNextButton() will return Calculator.OP_ADD. The third call will return 4, and the fourth call will return Calculator.EQUALS. Then, if you call your Calculator's getNextButton method again, it will wait for another button to be pressed before returning.
Each Calculator object also has its own getDisplay and setDisplay methods for interacting with the Calculator's display. (Note that the display, and these methods, operate using Strings, not a numeric type such as int.) Their signatures are as follows:
getDisplay() returns the String currently displayed on that Calculator's screen. Unlike getNextButton(), getDisplay() doesn't wait for something to happen (e.g., for the value to change) before returning it; it just tells you what text is currently being displayed.public String getDisplay();public void setDisplay(String s);
setDisplay replaces any currently displayed text with its single argument, a String.
Java has a built-in method to convert a String to a double. It throws NumberFormatException if the string you pass it doesn't represent a double. The signature looks like this:
public double Double.parseDouble(String s) throws NumberFormatException;
The opposite, converting a double to a String, is best accomplished by:
To figure out whether a char or String appears in a given String, usepublic String String.valueOf(double d);
public int String.indexOf( int ch);orpublic int String.indexOf( String str);
In lab, you will be writing a class that will interact with an instance of the Calculator object, and serve as a controller for it. You should name your class ButtonHandler, and it should be a self-animating active object. At each point in time, your class should ask the Calculator for the next button pressed, and then, if appropriate, perform any desired computations and update the Calculator's display. (Needless to say, it should also update its own internal state appropriately at each step.) Note: Your calculator should respond to each key as it is pressed. In particular, you should NOT use a strategy of storing all of the keypresses and parsing them when the = button is pressed.
As usual, you should follow an incremental design strategy -- keeping the end goal (a working calculator) in mind, but starting with simple implementations first. Before going to lab, think about how you would implement the following:
In order for your controller to be able to do anything, it's going to need a Calculator instance to work on. You can write a constructor for your ButtonHandler class that takes a Calculator as an argument. Remember to save the calculator in a field so that you can use it later.
We've left a space in the calculator.Main class for you to call your new constructor. Here's what the main method looks like:
public static void main(String[] args) { // create an instance of a calculator Calculator calc = new CalculatorGUI(); // create a controller for the calculator // [your code here] }
The class CalculatorGUI implements the Calculator interface. You should replace the // [your code here] with a statement that creates a new instance of your ButtonHandler class.
Inside your ButtonHandler constructor, you'll need to create a new Thread and start it. If you make your ButtonHandler class implement the Runnable interface, you can do this like so:
Thread anim = new Thread(this); anim.start();
The Runnable interface requires a single method,
public void run()
Unlike the act() method of our Animate class, which got called over and over,, the run() method of Runnable only gets called once by a Thread. This means that your run() method should loop forever, getting the next button and processing it.
Write out the complete constructor for your ButtonHandler and sketch out the contents of the run() method. Make sure you understand how a Thread works, and the differences between a Thread and an Animator.
Write code that will allow you to reset your calculator. For this purpose, you will probably find it useful to define a reset() method which you can call whenever you want to reset the calculator. Later on, you can then fill the body of reset() with the things you want to do when you reset (i.e., setting fields to their appropriate values). Remember that there is nothing wrong with an object calling its own methods. In fact, methods used in this way can be very useful for grouping commonly used code together, so that instead of rewriting the same code whenever you need it, you can just call the method.
Q How would you make the calculator reset (i.e., call the reset() method you have defined in your class) when then the CLEAR button is pressed, but not at any other time?
Remember, don't try to implement a complex calculator all at once. Break it down into pieces that you can test independently, and test them thoroughly before moving on to the next step.
At this point, you can now try to design a simple ButtonHandler that accepts only digit op digit =. Any other input should cause the ButtonHandler to reset itself. Map out the logic of this controller. At each step, be explicit about what appears on the Calculator's display. Since the operator does not appear on the display, you may want a place to store it. Be sure that you know where you'll keep each piece of information at each point along the way.
The target exercise is to have a calculator that understands at least digit op digit equals, along with a working clear button.
Think of examples that your code ought to work on at each stage, as well as examples where it might not behave as you would like. Write these examples down and experiment with them in lab.
Your code should define a class called ButtonHandler. Its constructor method should take a Calculator as its only argument. Your ButtonHandler should be an active object: it should have its own (activated) Thread. At each point in time, it should gobble up a button press (by calling the Calculator object's getNextButton() method) and, if appropriate, update the Calculator's display. (It should also update its own internal state at each step.)
You will almost certainly want to keep your central control loop small. This is best accomplished by spinning off methods to handle separate cases. By passing parameters, you can make these cases reasonably general. Remember that you don't want to be writing a lot of the same code many times. When this happens, it is a good hint that you've got a common pattern, and it's always a good idea to write down the common pattern, give it a name, and make it into its own method. But make sure that your common pattern makes sense, too. A good rule of thumb is: if you can't explain your code pretty clearly and concisely to someone else, it isn't designed right.
Details of the ButtonHandler specification, as well as hints for a stage-by-stage implementation, are given in the Laboratory section, below. Roughly speaking, it follows these steps:
When you begin to code, even though you may have constructed a sophisticated design for your ButtonHandler, you should always begin your lab-work with a simple, independently testable implementation and build on it as each stage works robustly.
For example, start with a very simple class with just a constructor that takes a Calculator as its only argument, and make sure that it compiles. Then, you can begin testing your class design, without any of the internal logic. Although it makes a pretty lousy calculator, a simple test that will help ensure that your infrastructure is working is to simply echo each button pressed back to its Calculator's screen.
Compile and run your code to make sure that you've built things correctly. If you have problems, now is a good time to work them out.
The target exercise is to have a calculator that understands digit op digit equals, along with a working clear button.
Remember that we think it's better to produce basic but elegant, well-tested, well-documented, and well-understood code than code with additional features but poorly written/documented/tested. We are more interested in how well you do what you do than in how much you do.
Record your experiences, including tests that various stages of your implementation succeed and fail. Make sure that you understand how your code behaves, and how it will behave under inappropriate as well as appropriate circumstances.