This lab is due 11:59pm on Sunday. There is Extra Credit available for this lab, described in the final section. Make sure you complete the lab before you start the extra credit.
Cleveland's Diner
Cleveland's Diner is a little greasy spoon squeezed into the corner of a high-rise on a busy thoroughfare. You've probably all eaten at a place like this. Cleveland's has a simple menu (only breakfast dishes). They need a system for taking orders and computing prices. In this lab you will turn the Diner menu into a collection of Java classes which represent the items offered at Cleveland's and collected together in a Java package.
The menu and prices (in dollars) at Cleveland's are as follows:
| Item | Price |
| Coffee Tea |
1.00 |
| Coke Diet Coke |
1.25 |
| Milk | 1.50 |
| Orange Juice Grapefruit Juice |
2.00 |
|
|
|
|
There are also several plate choices:
- Egg Plate: Choice of Egg, Meat, and Side item. Also comes with a choice of White or Wheat toast. Price: sum of the prices of the Egg, Meat and Side items.
- Pancake Plate: Choice of Pancake, Meat and Side item. Price: sum of the prices of Pancake, Meat and Side items.
- Huevos Rancheros: Choice of Egg topped with spicy chili sauce and served with beans and rice. Choice of Corn or Flour tortillas. Price: Price of Egg dish + $3.00.
Getting Started
Read carefully the following start instructions.
- Create a directory lab3 wherever you want to work.
- You will need to set the compiler for Eclipse to version 5.0. See here.
- Create a new project called Diner.
- Create a text file called Readme and put your name in it. You may write any comments you want me to see in this file.
- Create a new package inside Diner and call it menu (lower case 'm', or Eclipse will complain!)
Implementing The Diner Menu
What is Expected
In this section you will create the classes in your Java menu for Cleveland's Diner. This menu will consist of several Java classes:
- Drink
- Egg
- Pancake
- Side
- Meat
- EggPlate
- PancakePlate
- Huevos Rancheros
public interface Item {
public int price();
}
Each class you write will implement the Item interface. Each class will be evaluated based on the following:
- A class constructor whose parameters are determined by the data fields of the class.
- A
pricemethod for computing the price of the object in pennies. - A
toStringmethod for providing an informative and intelligible description of the object. - A
mainmethod which provides a thorough range of test cases. This is described in Testing. - Documentation of your public methods through public comments. This is explained in Commenting.
- Appropriate handling of inappropriate arguments to the constructor method. In this lab we will handle exceptions by convention, which is described further below. For Extra Credit you can handle inappropriate arguments by Java's Exception class.
- Code Style. Your data fields need to be of an appropriate type. Your code should be clear and concise. These are not unrelated. This is further described in Style.
public class Drink implements Item {
public enum Type{
COFFEE, TEA, MILK, ORANGE, GRAPEFRUIT, COKE, DIET
}
// private data field
private Type type;
/**
* Expects non-<code>null</code> reference.
*
**/
public Drink(Type type) { this.type = type; }
/**
* Compute the price of Drink
*
* @return whole number of pennies
*/
public int price() {
// TODO Auto-generated method stub
}
/**
* Produces String description of Drink.
* Examples:
* <ul>
* <li>coffee: "COFFEE"
* <li> coke: "COKE"
* </ul>
*
* @return String description of Drink object
*/
public String toString() {
// You must fill-in body
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
You are forced to write a price() method because Drink
implements Item. Java does not force you to write
toString() because every class is a subclass of the
Object class which has already implemented this method. I
included toString in Item to make you aware
that you will need to produce an informative description for any
object which is an Item.
You will also need to write a
constructor which initializes your private data field and a
main method where you test your new class.
Every class follows this form.
Creating Interface and Classes in Eclipse
To create a new interface Item inside the menu pacakage:
Create a new interface inside the menu package. (You can write-click on menu, or <ctrl> + mouse-click on the Mac. This gives you a menu, which you can choose Interface and call it Item.Eclipse will give you a wrapper
package menu;
public interface Item {
}
And you can fill-in the body of the interface with the methods
price.
The first line package menu; tells the compiler that
Item is part of the package menu. Any class
in this package may refer to any other class in the package by name.
To construct a new Drink inside the package:
Drink coke = new Drink(Drink.Type.COKE);
But outside the package, Java requires you include the
package name before the class: menu.Drink. This
week all your coding will be inside the menu package, next
week you'll see how to use your new package.
It is easy to create new classes in the menu package which implement the Item interface and have a main method. Try the following steps for the Drink class:
- Open a new class in the menu package.
- In the New Class Wizard, select add next to Interfaces, then type in the interface Item.
- Make sure you select the main method stub in the New Class Wizard.
- You are ready to add code.
-
package menu;at the top of the file. - A public class wrapper which implements Item.
- A method stub for the
printmethod. This was forced because you are implementing the Item interface. - A method stub for the
mainmethod, which Eclipse nicely gave you because you requested it. - You must add data fields, a constructor and the
toStringmethod, which will overwrite thetoStringmethod defined by the Object class.
Commenting Your Code
There are two sorts of comments in Java:
- Private comments:
//or/* */ - Public comments:
/** */
- Javadoc only reads public comments.
- Javadoc has several keywords, and these begin
with an "@". For example
@returnor@author. - Javadoc knows HTML, and uses HTML to format its comments.
- You can see your public comments interpreted by Javadoc in Eclipse by hovering over a method which has a public comment, or by clicking Javadoc in the Console Pane of Eclipse (the window which produces output from your program.)
Handling Bad Arguments by Convention
Suppose a user of your Drink class tries to create an
object by passing your constructor a null reference?
It is impossible for your constructor to produce an object.
Java provides a mechanism for handling these kind
of problems
with the Exception class.
As Extra Credit you
may handle bad arguments by using Exceptions. You should only
attempt this after you finish writing your class code. For now,
we will handle bad arguments by convention. This means that
you will document as part of your class the range of acceptable
arguments (or what arguments are unacceptable), and guarantee your
methods will work for these arguments. You
document this in your public comment before constructors.
The user agrees by using
your code, by contract, that only acceptable arguments will
be passed. The user accepts responsibility
fo all the awful consequences of passing
unacceptable arguments.
Testing Your Code
All code you write must be thoroughly tested using a
test driver (a main method) inside each class. You must
prove to me your code works correctly, by showing that it correctly
works on any possible use of your class. This means you will have to
carefully consider how your class could be used. You may
assume that all constructors are called with correct parameters, so
you can create intelligible objects. When you do the
Extra Credit you can handle the exceptional
cases where your constructor is called with incorrect arguments.
Test each class before you write the code for the next class.
The Drink class has only seven possible objects which could be created, so it is reasonable to test each instance. Here is an example of how to write your test driver to test two of the seven possibilities:
public static void main(String[] args) {
// Expected output:
// COKE 125
Drink coke = new Drink(Drink.Type.COKE);
System.out.println(coke + "\t" + coke.price());
//Expected output:
// COFFEE 100
Drink coffee = new Drink(Drink.Type.COFFEE);
System.out.println(coffee + "\t" + coffee.price());
// You write 5 remaining cases
}
Sometimes a class has too many instances to realistically test all. For example, the Pancake class allows a choice of the number of Pancakes as well as the type. In this case you only need to test a few of the possible integers for each of the types. Remember, you may assume the number is intelligible, which means one or more, until you tackle the exceptional cases. Another example is the PancakePlate class which includes several other classes among its data objects. Since you have already tested each of these classes, you will only need a few instances of the PancakePlate class to show your code is correct.
Coding with Style
It is very important that you try to make your code brief and clear. Code style is very important, since code which is obscure and hard to read, often reflects confusion in the programmer and is likely to be buggy. I have links to Style, and you should take the time to implement the good practices recommended into your code writing.
Read this paragraph carefully. There are only a few types
of drinks possible at Cleveland's, so using an enum class
is appropriate. It has the value that the only possible bad
argument to the constructor is the null reference. On
the other hand, the number of eggs a customer would like is to be kept
open ended. Why force a customer to order three eggs at a time?
William "The Refrigerator" Perry claimed to eat 27 eggs
(scrambled) at one sitting!! In this case an integer is appropriate to
store the number of eggs. By implementing the Drink type as
an enum, you have the added bonus of being able to use
the switch statement to make your code clearer:
switch (type) { // type is a Drink.Type
case COFFEE: case TEA:
// do stuff
break;
case COKE: case DIET:
// do stuff
break;
case MILK:
// do stuff
break;
case ORANGE: case GRAPEFRUIT:
//do stuff
break;
default:
// This case can only arise if type is a null reference
}
Handling Exceptions
Handling exceptions only requires adding a few lines of code to each class, and adding another test case or two to your test driver. Make sure your classes work in the expected cases before you attempt to handle the exceptional cases. You should read Chapter 9 (Error Handling with Exceptions) of Thinking in Java by Bruce Eckel, for a more detailed description of exceptions in Java.
What should your Drink constructor do if the user passes
a null reference for a Drink.Type?
You could ignore it, but you would
not be creating an intelligible object. Java provides
a special Exception class,
NullPointerException for just this possibility. Here
is how you can rewrite your constructor for the Drink class:
/**
*
* @exception NullPointerException a null reference
*/
public Drink(Type t) {
if (t == null)
throw new NullPointerException();
else
this.type = t;
}
If the user passes null to this constructor, no
Drink object will be created, but an exception is
raised, which can be used to signal the method which attempted
to create a Drink object that no object was created. If
t == null, then Java creates a
NullPointerException object and throws this
object--which means the constructor terminates on the spot.
In addition to including the new code, you need to document that
your constructor throws an exception. This is placed in a public
comment using @exception:
@exception [Exception thrown] [Exception description]
In order to recieve the thrown exception, a method must inform the compiler that it wishes to catch the signal. Here is an example of how this could be done in your test driver:
// Expected output:
// COKE 125
// Unless a NullPointerException is raised.
Drink.type t; // t == null
try { // try block
Drink coke = new Drink(t);
System.out.println(coke + "\t" + coke.price());
} catch ( NullPointerException e) { // catch block
System.err.println("Whoops!! Passed a null type.");
}
The try block is where you try to create
a Drink object, but you want to catch an
exception if this attempt fails. The catch block
does two things:
- It says you want to catch a NullPointerException,
and creates a reference for a NullPointerException
object called
e. This object is typically not used, since usually all you need to know is what exception was raised. - It says what you will do if a NullPointerException is raised. In this case, print a message.
System.err.println(String s)
Normally, output is written to Standard output,
or System.out in Java. This is typically the terminal
(or console in Eclipse), but it may be a file. The user expects
that error messages (which are not expected as part of the output)
to be sent to Standard error, or System.err
in Java.
How do you handle exceptional arguments to your constructor in a class like the Pancake class? There are two sorts of problems here:
- Passing a
nullPancake type, for which you raise a NullPointerException. - Requesting less than one pancake. (You cannot have a negative number of pancakes, and requesting zero pancakes is stupid, and probably a mistake.) Java provides an exception called IllegalArgumentException (remember IAE from class) for this purpose.
catch blocks, one for each exception:
Pancake.Type t = Pancake.Type.PLAIN;
int n = -1; // stupid value
try { // try block
Pancake p = new Pancake(t, n);
System.out.println(p + "\t" + coke.price());
} catch ( NullPointerException e) { // catch block
System.err.println("Whoops!! Passed a null type.");
} catch ( IllegalArgumentException e) { // catch block
System.err.println("Whoops!! Passed a bad number.");
}
Handin
Your lab is due Sunday at 11:59pm. You will submit your Diner project folder. You must be inside this directory to submit. See handin for details
Kenneth Harris