Hands on Testing Java: Lab #6

Java Classes and Objects

Question #6.01 After reading through this lab (which you did to answer the prelab questions), how long do you estimate it will take you? Be honest with your answer, and do not worry if it's nowhere close.
To be answered in Exercise Questions/lab06.txt.

Question #6.02 Record how long it takes you to actually complete this lab. Use this question (in the answer file) to keep track of your start and stop times. Be sure to subtract out your minesweeper breaks.
To be answered in Exercise Questions/lab06.txt.

Introduction

Most of the problems in the previous lab exercises had relatively simple solutions because the data objects in the problem could be represented using the predefined Java types. We can represent a volume with a double, a name with a String, a vowel with a char, and so on.

However, nearly all real-world problems involve data objects that cannot be directly represented using the predefined Java types.

Your Problem: Fractions

For example, suppose that you know a certain gourmet chef named Pierre whose recipes are for 12 servings. Of course, it's rather rare that exactly 12 people come in and order exactly the same thing, so Pierre frequently must scale his recipes for the number of people who do come in (e.g., 1 customer gets 1/12th of a recipe, 2 customers get 1/6th of a recipe, 15 customers get 5/4ths of a recipe, etc.). The recipes use fractions for some ingredients (e.g., 1/2 tsp. salt, 3/4 cup flour, etc.) so that he must multiply fractions to scale a recipe (e.g., 5/4ths of 1/2 tsp.).

Pierre comes and asks you to write program to help in the scaling.

My Problem: Two-Dimensional Coordinates

Throughout this lab, I will also work on another example in complete detail. This problem is to represent two-dimensional coordinates, (x, y).

There are a lot of similarities in the implementation of the solutions for fractions and for coordinates, but there are significant differences as well. So in writing your own code, look at the generic code patterns and my specific solution for coordinates. Then build an answer for your fraction solution.

Getting Started

Do this...

Creating Classes

In Java, a new data type can be created with a class:

class-declaration pattern
public class ClassName {
}
  • ClassName is the name of the new data type.
  • The name of a class follows the same conventions as a variable except that the first letter of the class name is capitalized.

Helpful hint: You must not use the static keyword at all in this lab. If you can "fix" your code with static, don't! Make sure there are no statics in your code, and find another solution.

You've used classes in the past to store away static methods, but this is the lab when you put static methods behind you. You're going to use classes the way they were intended: as blueprints for new objects.

All of the reference data types that you've already used---like Keyboard, Screen, and String---come from a class. In fact, as far as Java is concerned, the classes you define are just as good as these other classes no matter where they come from.

An object consists of two things:

  1. the object's attributes---the data encapsulated inside the object, and
  2. the object's operations---the actions that can be invoked on the object.

For example, the Screen must have a connection to the screen itself. This connection is data. Somehow a Screen object must keep track of this data. A Screen object also has actions we perform on it, like print() and printFormatted(). Similarly, a String is a string of characters, a bunch of things; and a String has a whole variety of operations we can invoke on it.

Instance Variables

An attribute of an object is a data item necessary to represent the object. These attributes are coded up as variables in our class, but in a slightly different way than the variables you've seen before.

Let's consider my two-dimensional-coordinates example. Such coordinates comes in this form: (x, y); e.g., (2, 3), (3,14, 2.8), etc.

Hence, a two-dimensional coordinate has two attributes: an x-coordinate and a y-coordinate, both real numbers. I must keep track of both the x- and y-coordinates, otherwise I'm not storing a coordinate.

An attribute is implemented as an instance variable in a class:

public class Coordinate {

  /**
   * The x-coordinate.
   */
  private double myX;

  /**
   * The y-coordinate.
   */
  private double myY;

}

You should put a Javadoc comment in front of each instance variable; it does not have to be elaborate.

Instance variables are a little different than other variables. By convention, all of the attributes of an object are grouped together at the beginning of the Java class (not inside a method).

The pattern is mostly the same as any other variable declaration:

instance-variable pattern
private type identifier;
  • type is any data type.
  • identifier is the name of the instance variable.
  • In addition to the conventions for all variables, we will use a my prefix to re-enforce an internal perspective.

The basic pattern of "data type followed by identifier" is still there, but we typically do not initialize instance variables. You can initialize an instance variable (with the same syntax as for a local variable), but generally instance variables are initialized in a constructor which we'll get to in the next section of this lab.

One significant difference is the location of the declaration of the instance variables. The declaration is in the class, not inside a method. So you now know about three kinds of variables:

Another difference between an instance-variable declaration and other variable declarations is the keyword private. The keyword private describes the visibility of the instance variable; in other words, "who else can see this variable?" Private information is kept just to yourself, and that's exactly what private visibility means for an instance variable.

In contrast, you declare your classes and methods as public for public visibility: these classes and methods are open to the public; anyone can access them.

As noted in the pattern, our convention for naming instance variables will be the same as for all variable identifiers except we'll add a "my" prefix. This will be our clue that these variables are instance variables. (This prefix means nothing to the compiler, although your IDE may make use of them.) Read the "my" as personal ownership, because we'll be using internal perspective to write our OCDs, and when we do this later in this lab exercise, you'll see how the "my" prefix is beneficial.

The Fraction Class

Now let's turn to your fraction problem. First you need to identify the attributes of a fraction. Every fraction has this form:

numerator/denominator

For example, 1/2, 5/4, 37/1002, etc. Both the numerator and denominator are integers.

Only the numerator and denominator are the attributes of a fraction. The slash / isn't an attribute because every fraction has one. The attributes of a class are only the objects needed to uniquely represent each unique instance.

Do this...
Define two integer instance variables named myNumerator and myDenominator in the Fraction class. Write Javadoc comments for both variables.

Helpful hint: Use the Coordinate example to write your Fraction code. However, never ever forget that these two problems are very different (as well as being quite similar). Note how their forms are different; note how their attributes are different.

Consider the declarations of oldMeasure and scaleFactor in PierreCLIDriver#main(String[]). When Fraction objects are created for them, they can be pictured like this:

fraction

Each instance of Fraction has its own copy of each of the attributes we defined.

Instance Methods

A class also has methods which provide operations for the instances of a class. These methods are different from the static methods we have seen before because they will work on the attributes of an instance.

The instance variables of a class are kept private so that a program using the class is not permitted to access them directly. This is a good idea because a program that directly accesses the instance variables of a class becomes dependent on those particular instance variables. If those instance variables are changed (which is fairly common), then these other programs must also be changed, increasing the cost of software maintenance. Also, we may want to assume certain things about our instance variables, and if everyone has access to them, they can do bad things to our data.

On the other hand, we want users of the class to be able to perform operations on the instances. As a result, methods should usually be declared as public.

You've already seen how to declare class methods; the only change in writing instance methods is that we drop the word static. Here's the pattern:

instance-method-declaration pattern
public returnType methodName(parameters) {
  bodyStatements
}
  • The public allows all other classes access to the method.
  • returnType is the data type of the value that the method returns. If the return type is void, no return statement is necessary.
  • methodName is the name of the method.
  • parameters is a list of declarations of parameters.
  • bodyStatements is the body of the method, consisting of program statements.

It's hard to see something that's been omitted, but note that the word static never appears here. If you flip back to Lab #4, you'll see that's the only change to the method declaration compared to a class method declaration.

But that one change makes all the difference. Instead of being invoked on the class, an instance method is invoked on instances. For example, suppose two String objects named greeting and closing are defined like so:

String greeting = "hello",
       closing = "goodbye";

The String class has an instance method named length() that has no parameters. Since it's an instance method, this code is invalid:

String.length()

The compiler will complain that you're trying to invoke an instance method in a "static context" (or something along those lines). Think about it: you want the length of what string? You have to specify the exact string whose length you want.

Instance method are a way to ask a specific instance like so:

greeting.length()

and

closing.length()

The first expression evaluates to 5 and the second to 7.

The general pattern for invoking an instance method is this:

instance-method-invocation pattern
instance.methodName(arguments)
  • instance is an instance of a class (often expressed as an identifier).
  • methodName is the name of the method from the class of the instance.
  • arguments is a comma-separated list of expressions.

Object-oriented programmers like to think of calling a method as sending a message to an object. When we send the length() message to greeting, greeting responds with the number of characters it contains; when we send the same length() message to closing, closing responds with the number of characters it contains.

Notice how the focus is on the objects. We ask the greeting and closing objects for their lengths. We do not ask the String class. This is because greeting knows what characters it has stored away and it knows its length. The String class doesn't know or even care.

An Output Method

To facilitate debugging a class, it is often a good idea to begin with a method that can be used to display class objects.

Consider the Coordinate class again. I could write a print() method that prints the coordinate to a Screen, but this would mean that anyone using my Coordinate class will also need the Screen class. It'd be better if I could just send a Coordinate object as a parameter to a print() method like so:

theScreen.println("The coordinates are: " + coord);

There's a similar line in your driver.

Java allows us to do this fairly simply. The key here is the + operator. When applied to Strings, it's the concatenation operator. The problem is that coord and newMeasure are Coordinate and Fraction objects, respectively, not Strings.

Since this is such a common problem, there's a built-in solution in Java: if an operand to + is not a String, Java turns it into a String automatically as long as we define a toString() method for our class. Java will take care of the rest. (Actually, toString() has default definitions in Coordinate and Fraction, but they're not particularly useful.)

Java specifies the toString() specification like so:

Specification:

receive: nothing.
return: a String representation of the object.

Coordinate#toString() should return a String representation of the Coordinate instance. The specification doesn't tell the whole story, though. Let's consider the behavior of Coordinate#toString().

Before we get to this behavior, remember the internal perspective mentioned above. When specifying the behavior of an instance method, use the first person to describe the behavior. Imagine yourself as an instance of the object; even state your identity at the beginning of the paragraph:

Behavior of Coordinate#toString()

I am a Coordinate object. When I receive a message to return my String representation, I will concatenate these Strings: "(", my x-coordinate, a ",", my y-coordinate, and a ")".

Because I use an internal perspective, my attributes pop out of the behavior paragraph. It's very natural for me to talk about "my x-coordinate". Where might I find this value? In myX, of course!

Objects of Coordinate#toString
Description Type Kind Movement Name
literal strings String literals local "(", ",", ")"
my x coordinate double instance variable instance myX
my y coordinate double instance variable instance myY
concatenated string String varying out result

I don't put an object entry in the objects table for "I" or "me". The members of the "me" object that are needed will be explicitly mentioned in the behavior paragraph (like "my x-coordinate" and "my y-coordinate"), and those should be included in the table.

Compare the object table to the Java-mandated specification above.

Operations of Coordinate#toString()
Description Predefined? Name Library
concatenate strings yes + built-in
return the concatenation yes return built-in

The algorithm is rather simple:

Algorithm of Coordinate#toString()
  1. Let result be the concatenation of "(", myX, ",", myY, ")".
  2. Return result.

As code, it looks a lot like code we've written before:

public String toString() {
  String result = "(" + myX + "," + myY + ")";
  return result;
}

The only difference from previous methods is that the instance variable are used without a local declaration. The instance variables are declared once in the class, never again.

This method goes in the Coordinate class, after the instance variables.

One of the most important things to note about an instance method is that I am not declared (using the language of an internal perspective). In more proper object-oriented terms, the instance is not declared. We get the instance and its instance variables for free. A Coordinate instance has a myX and a myY that it gets for free in this method. These instance variables are accessed directly, without any other declaration.

This point cannot be emphasized too much because you will forget this and try to redeclare the instance variables. (Everyone does.) Look at the Coordinate#toString() method again and note that myX and myY are never explicitly declared in that method. We get them for free in the method since they've been declared as instance variables in the same class.

We do have to declare the variables, but only once as instance variables at the beginning of the class.

Question #6.03 Write a good behavior paragraph for Fraction#toString(). Keep in mind that the form (i.e., the punctuation) of a coordinate looks different than that of a fraction.
To be answered in Exercise Questions/lab06.txt.

Do this...
Declare a toString() method for your Fraction class.

Do this...
Write a Javadoc for the method, and compile your code.

Before we can run or test anything, though, we have to first be able to construct instances.

Constructors

Creating and initializing an object is known as constructing an object (or instance). Java allows us to define a special method called a constructor that specifies exactly what to do when an instance is created. We can use the constructor to initialize the instance variables of the instance.

A constructor does not return anything to its caller; it cannot return anything. It initializes the attributes of an object when that object is created. It's a subtle distinction, but think of new as creating a new instance and returning the new instance, but returning it only after the constructor initializes it. This is very important in the syntax for a constructor.

The simplest constructor is the default constructor. A default constructor does not have any arguments or parameters.

To call the default constructor for my Coordinate class, I would write this:

Coordinate coord = new Coordinate();

Defining a constructor is just a little different from a method. The design is similar (i.e., OCD), but the responsibility is much more focused that with a normal method.

Behavior of Coordinate#Coordinate()

I am a Coordinate object. My default initialization will set my x-coordinate to 0.0 and my y-coordinate to 0.0.

Objects of Coordinate#Coordinate()
Description Type Kind Movement Name
my x-coordinate double instance variable instance myX
my y-coordinate double instance variable instance myY
zero double literal local 0.0
Operations of Coordinate#Coordinate()
Description Predefined? Name Library
set a variable yes assignment expression built-in

Since all of the action of a constructor is internal---nothing is returned, nothing is printed---the specification of a constructor describes the constructor's postcondition. That is, describe what should be true when the constructor is finished. It does not give the actual computation code, but the computation code is usually strongly implied.

Specification of Coordinate#Coordinate():

postcondition: myX == 0.0 and myY == 0.0.

So we implement a default constructor with public access in the class Coordinate like so:

public Coordinate() {
  myX = 0.0;
  myY = 0.0;
}

Just like an instance method, a constructor has free access to the class's instance variables.

Constructors are commonly placed after the instance variables and before the instance methods.

Here's the general pattern for a constructor declaration:

constructor-declaration pattern
public ClassName(parameters)
{
  bodyStatements
}
  • ClassName is the name of the class (exactly the same!).
  • parameters is a list of parameters (just like a method).
  • bodyStatements is a sequence of statements that initialize the instance variables of the class.

Do this...
Using this information, define a default constructor for your Fraction class, that satisfies the following specification:

Specification:

postcondition: myNumerator == 0 and myDenominator == 1.

Since division by zero is undefined, do not set myDenominator to be zero. Any non-zero number would be okay, but 1 is pretty standard.

Do this...
Write a Javadoc for the constructor (just like a method); the postcondition actually gives you a perfect description of the constructor for the comment; just write it in English, not mathematically. Then compile your code.

Testing the Default Constructor and toString()

Once your code compiles fine, we can start testing both the default constructor and the toString() method.

Consider the Coordinate class. We could test this in a JUnit test case named CoordinateTest.

Now that we're working with our own instances of our own classes, we can actually use instance variables in our test-case classes. So the first thing I do in my CoordinateTest class is to declare three Coordinate instance variables:

private Coordinate myCoordinate1;
private Coordinate myCoordinate2;
private Coordinate myCoordinate3;

The convention for JUnit is that we use a method named setUp() to initialize the instance variables (rather than using a constructor). This is so that setUp() can be called before every test-case method.

So I define this method like so in my CoordinateTest class:

@Override
public void setUp() {
  myCoordinate1 = new Coordinate();
  myCoordinate2 = null;
  myCoordinate3 = null;
}

Capitalization matters. If you define setup() rather than setUp(), your tests won't work right. The @Override should help; it should make sure that you're writing the right version of setUp().

I'll return later and initialize myCoordinate2 and myCoordinate3 with a useful and interesting values later.

Now I can test:

public void testToString() {
  assertEquals("string version of origin", "(0.0,0.0)", myCoordinate1.toString());
}

This test method itself is mostly the same as the other test methods we've written. The main difference now is working from an instance (myCoordinate1), not a class (Coordinate).

This test answers this question: when I construct a Coordinate using the default constructor, what should its toString() return? Looking at the definitions, it's clear that the default constructor will construct a Coordinate object whose toString() will return "(0,0)".

The setUp() method should look something like this:

setup-method pattern
@Override
public void setUp() {
  initializations
}
  • initializations are assignment statements initializing the test-case class's instance variables.

The @Override will generate an error if you misspell or miscapitalize "setUp". If will also generate an error if you forget to create a true test-case class.

Do this...
Create three instance variables, a suitable setUp() method, and a testToString() method in the FractionTest class. Add a Javadoc comment to each of these things. Then compile and run the test-case class for a green bar.

Make sure your testToString() really tests the way a Fraction is supposed to look: 0/1. Don't get distracted by the extra punctuation used for a Coordinate.

A Second Constructor

At this point I have the ability to create objects for the coordinate (0,0), and you can create objects for the fraction 0/1. These objects aren't going to get us far in life. We need to be able to specify other values for the x- and y-coordinates and for the numerator and denominator.

As mentioned above, objects are created by new and initialized with a constructor. Presumably, we need another constructor.

Behavior of Coordinate#Coordinate(double,double)

I am a Coordinate object. To explicitly construct me, I will receive x- and y-coordinates. I will use these values to initialize my own x- and y-coordinates.

Values are passed around as arguments and parameters. When I receive an x-coordinate, it is not mine yet! I receive it generally (e.g., x, and if it's okay, then I'll use it to initialize my x-coordinate.

Objects
Description Type Kind Movement Name
an x-coordinate double varying in x
a y-coordinate double varying in y
my x-coordinate double instance variable instance myX
my y-coordinate double instance variable instance myY

The operations are "receiving" and "setting"---parameters and assignment expressions, respectively.

Algorithm of Coordinate#Coordinate(double,double)
  1. Receive x and y.
  2. Set myX equal to x.
  3. Set myY equal to y.

My specification indicates, again, what I expect to be true after the constructor executes:

Specification:

receive: x and y, two double values.
postcondition: myX == x and myY == y.

However, a constructor's name is the same name as the class. We already have one constructor named Coordinate for the Coordinate class, how can we have another? Fortunately, Java allows us to reuse a method name like this as many times as we want so long as the number or types of the parameters differ. Defining the same method (or constructor) multiple times is called overloading the method (or constructor).

The two constructors for the Coordinate class are different because the default constructor has no parameters while this new one has two parameters:

public Coordinate(double x, double y) {
  myX = x;
  myY = y;
}

Any constructor with one or more parameters is known as an explicit-value constructor because it takes explicit values to initialize the instance.

Now I can create any Coordinate I want:

myCoordinate2 = new Coordinate(1.2, 3.4);
myCoordinate2 = new Coordinate(9.8, 7.6);

Do this...
Define a second Fraction constructor that satisfies this specification:

Specification:

receive: numerator and denominator, two integers.
precondition: denominator != 0.
postcondition: myNumerator == numerator and myDenominiator == denominator.

The precondition should be checked like so at the very beginning of your constructor:

if (denominator == 0)
  throw new IllegalArgumentException("Denominator must be non-zero.");

Be sure to test the denominator parameter and not the instance variable. The instance variable will always be 0, and so the test would always fail! The purpose of this test is to make sure that the canditiate denominator is acceptable. If it's zero, it's unacceptable, and we'll never initialize myDenomiator to that candidate.

This precondition for the constructor is also an invariant for the class. An invariant is a boolean condition that should be true for all instances (i.e., before and after each method is executed). For all Fraction objects, the denominator must be non-zero. We generally enforce an invariant as a precondition for a constructor which then re-assures us that it's true for all instances.

Do this...
Write a good Javadoc comment, and compile your code.

Testing Explicit-Value Constructor and toString()

We've already tested toString() with the default constructor, but maybe we just got lucky there. We should also test it with our new explicit-value constructor.

This is pretty straightforward: whatever values we use to construct a Coordinate, we should expect those same values to be put into the String returned by toString().

First I set my other Coordinate instances to more interesting values in CoordinateTest#setUp():

myCoordinate2 = new Coordinate(1.2, 3.4);
myCoordinate3 = new Coordinate(9.8, 7.6);

In testToString(), I write a two more assertions:

assertEquals("(1.2,3.4)", myCoordinate2.toString());
assertEquals("(9.8,7.6)", myCoordinate3.toString());

Do this...
Make similar changes and additions to your FractionTest class. You should have five instance variables in all: the first one uses the default constructor (as you already have it); use the following fractions for the other four instance variables: 1/2, 3/4, -2/3, 10/7. Compile and run for a green bar.

The values picked here are not completely arbitrary. To keep yourself on track with this lab exercise, you should use exactly these values.

Testing Invariants

Unlike my Coordinate instances, you do have a restriction on your Fraction instances: their denominators cannot be zero. You need to test to see if this is being enforced by the new constructor.

Here's the whole test method you need for the FractionTest class:

public void testFailedInstances() {
  try {
    Fraction fraction = new Fraction(1, 0);
    fail("constructed a fraction with zero denominator");
  } catch (IllegalArgumentException e) { }
}

Do this...
Add this method to your FractionTest class. Compile and run for a green bar.

We saw this technique before in testing preconditions. Remember that we expect the call to the constructor to fail which generates an exception that skips over the call to fail(). It's only when the constructor fails to fail that fail() will be executed. (Say that ten times really fast!)

You already have the precondition check in the constructor, so this should pass right away without any other changes.

Accessor Methods

Accessors are methods that retrieve an attribute of the object. These are often simple one line methods.

For example, it might be useful to be able to extract the x- and y-coordinates of a Coordinate object. These tasks have simple specifications and definitions that can be done in parallel here:

x-coordinate accessor y-coordinate accessor
Specification:
return: my X-value.
Specification:
return: my Y-value.
public double getX() {
  return myX;
}
public double getY() {
  return myY;
}

Accessors must go in the class (as all methods do); conventionally they are put in after the constructors.

Conventionally, accessor methods begin with a get prefix to indicate that we're getting a value from the instance, often just the value in an instance variable. This is in contrast to setting the value of an instance variable or to computing a value from the instance variables. (Incidentally, this get prefix is pretty much universally held; the my prefix on instance variables is less common.)

I test my new accessors:

public void testGetX() {
  assertEquals(0, myCoordinate1.getX(), 1e-8);
  assertEquals(1.2, myCoordinate2.getX(), 1e-8);
  assertEquals(9.8, myCoordinate3.getX(), 1e-8);
}

public void testGetY() {
  assertEquals(0, myCoordinate1.getY(), 1e-8);
  assertEquals(3.4, myCoordinate2.getY(), 1e-8);
  assertEquals(7.6, myCoordinate3.getY(), 1e-8);
}

Test each accessor on every instance. Write one test method for each accessor to make it easier to debug (and to inflate the number of tests!).

Do this...
Add accessor methods getNumerator() and getDenominator() to Fraction. And write Javadoc comments for them. Then add new test methods testGetNumerator() and testGetDenominator() to your FractionTest class. Test your accessors like the Coordinate accessors were tested above.

Remember the difference in data types between the instance variables in your Fraction class and in my Coordinate class. This has implications for your accessors themselves as well as what version of assertEquals() you use.

Do this...
Compile and run your test case for a green bar.

Input

Input is, quite frankly, annoying. Reading in a coordinate or fraction can be done, and it's actually rather easy if we assume we always get good input. That's what we'll assume.

Suppose that I wanted to read in a Coordinate like (3,4). Since input is so tricky and so particular, I will create a separate class to handle the input of a Coordinate.

I describe the problem like so:

Behavior of CoordinateInput#read(Keyboard)

I am a CoordinateInput object. To read a coordinate object, I will receive a keyboard, and then I will read in five things, in order:

  1. A left parenthesis.
  2. An x-coordinate.
  3. A comma.
  4. A y-coordinate.
  5. A right parenthesis.

I return a new coordinate based on the x- and y-coordinates that I read in.

The problem with this method is all of the extra things I need to read in addition to the two coordinate values. The Keyboard class does not work well reading in the separate punctuation characters separate from the numbers. Instead, if I insist that a coordinate has no spaces in it, but has spaces before and after it, I can use a Keyboard object to read in a whole word (i.e., all characters up to the next whitespace character). Java provides a StringTokenizer class which will break up the word into its components so that I'll have access to the two coordinates that I want. I can also check to make sure that the punctuation marks are also there.

Quite frankly, there are so many other interesting things in Java to look at, that it is not worth the time for us to explore the StringTokenizer in depth.

So without further ado, here's the method I write:

public Coordinate read(Keyboard in) {
  String input = in.readWord();
  StringTokenizer parser = new StringTokenizer(input, "(,)", true);
  if (!parser.nextToken().equals("("))
    throw new RuntimeException("Bad format for coordinate: " + input);
  double x = Double.parseDouble(parser.nextToken());
  if (!parser.nextToken().equals(","))
    throw new RuntimeException("Bad format for coordinate: " + input);
  double y = Double.parseDouble(parser.nextToken());
  if (!parser.nextToken().equals(")")
    throw new RuntimeException("Bad format for coordinate: " + input);
  return new Coordinate(x, y);
}

Input is always nasty, as this method demonstrates quite well.

If you're curious what's going on with this code, read the Java API to find out more about the StringTokenizer class (found in the java.util package). You should also look up the Double#parseDouble(String) method.

Once CoordinateInput#read() is defined, I can read in a Coordinate like so:

CoordinateInput input = new CoordinateInput();
Coordinate coordinate = input.read(theKeyboard);

Question #6.04 Write a behavior paragraph for FractionInput#read(Keyboard).
To be answered in Exercise Questions/lab06.txt.

This code should satisfy your needs:

public Fraction read(Keyboard in) {
  String fraction = in.readWord();
  StringTokenizer parser = new StringTokenizer(fraction, "/", true);
  int numerator = Integer.parseInt(parser.nextToken());
  if (!parser.nextToken().equals("/"))
    throw new RuntimeException("Missing the slash");
  int denominator = Integer.parseInt(parser.nextToken());
  return new Fraction(numerator, denominator);
}

You'll have to import ann.easyio.Keyboard and java.util.StringTokenizer.

Do this...
Implement FractionInput#read(Keyboard).

Testing the Input

It's difficult to test keyboard input with a JUnit test case. So let's turn our attention to the driver in the PierreCLIDriver class.

Do this...
Uncomment the lines in the main() method of PierreCLIDriver that declare and read in values for oldMeasure and scaleFactor. Temporarily, add this line at the end:

theScreen.println("The fractions are " + oldMeasure + " and " + scaleFactor);

Compile all of your code, and run the driver.

Be sure to test this driver several times on different fractions to make sure the input method works correctly. Also test it on a zero denominator (which should cause the driver to crash (i.e., quit with an exception)).

Fractional Multiplication

What about some real computations?

I might want to add two Coordinates:

coordinate1.add(coordinate2)

This is thought of as sending the add message to coordinate1, with coordinate2 as a message argument. The result that's returned should be a new Coordinate set to the sum of coordinate1 and coordinate2.

Computationally, this would be

(x1,y1) + (x2,y2) = (x1+x2, y1+y2)
Behavior of Coordinate#add(Coordinate)

I am a Coordinate object. I receive another coordinate object. To produce the sum, I construct a new coordinate: (my x-coordinate plus the received-coordinate's x-coordinate, my y-coordinate plus the received-coordinate's y-coordinate). I return this sum.

Objects of Coordinate#add(Coordinate)
Description Type Kind Movement Name
the received coordinate Coordinate varying in coordinate2
the sum Coordinate varying out sum
my x-coordinate double instance variable instance myX
my y-coordinate double instance variable instance myY
coordinate2's x-coordinate double accessor call local coordinate2.getX()
coordinate2's y-coordinate double accessor call local coordinate2.getY()
the sum's x-coordinate double varying local x
the sum's y-coordinate double varying local y

I could store the x- and y-coordiantes from coordinate2 in local variables, but the accessor methods are so short that it's just easier to use them directly. Since an accessor method gives you direct access to data, it's fair to consider it an object rather than an operation (although technically they are methods are so are operations).

I do store the sum's coordinates in local variables because I'm constructing that value in this method.

Operations of Coordinate#add(Coordinate)
Description Predefined? Name Library
receive a coordinate yes parameter mechanism built-in
add doubles yes + built-in
construct a new coordinate yes Coordinate constructor Coordinate
Algorithm of Coordinate#add(Coordinate)
  1. Receive coordinate2.
  2. Let x be myX plus coordinate2.getX().
  3. Let y be myY plus coordinate2.getY().
  4. Let sum be a new Coordinate constructed using x and y.
  5. Return sum.

So my resulting method is this:

public Coordinate add(Coordinate coordinate2) {
  double x = myX + coordinate2.getX();
  double y = myY + coordinate2.getY();
  Coordinate sum = new Coordinate(x, y);
  return sum;
}

And then I need to test my method. I could write this:

public void testAdd() {
  Coordinate sum = myCoordinate1.add(myCoordinate2);
  assertEquals(1.2, sum.getX(), 1e-8);
  assertEquals(3.4, sum.getY(), 1e-8);
  sum = myCoordinate2.add(myCoordinate3);
  assertEquals(11.0, sum.getX(), 1e-8);
  assertEquals(11.0, sum.getY(), 1e-8);
  sum = myCoordinate3.add(myCoordinate2);
  assertEquals(11.0, sum.getX(), 1e-8);
  assertEquals(11.0, sum.getY(), 1e-8);
}

However, this is clumsy. It's much better if I write a helper method first:

public void assertCoordinate(String message, Coordinate expected,
    Coordinate computed) {
  assertEquals(message, expected.getX(), computed.getX(), 1e-8);
  assertEquals(message, expected.getY(), computed.getY(), 1e-8);
}

Then my test method is much simpler:

public void testAdd() {
  assertCoordinate("first plus second", new Coordinate(1.2, 3.4),
      myCoordinate1.add(myCoordinate2));
  assertCoordinate("second plus three", new Coordinate(11.0, 11.0),
      myCoordinate2.add(myCoordinate3));
  assertCoordinate("third plus second", new Coordinate(11.0, 11.0),
      myCoordinate3.add(myCoordinate2));
}

Now my tests read as if I'm testing Coordinates, not x- and y-values.

While it is useful to define all of the arithmetic methods for a Fraction, the particular operation that we need in order to solve our problem is multiplication (the others are left as exercises).

You have this expression in the driver:

oldMeasure.multiply(scaleFactor)

This should multiply the two Fraction objects oldMeasure and scaleFactor.

The multiplication of two fractions is a little more involved that coordinate addition:

 
 n1     n2     n1 * n2
---- * ---- = ---------
 d1     d2     d1 * d2

That's essentially the behavior of the multiply() method, except you want to keep in mind the internal perspective---n1 and d1 are actually my values (because I'm a Fraction object). The n2 and d2 come from another Fraction object that I receive.

The specification for such an operation can be written as follows:

Specification:

receive: fraction2, a Fraction operand.
return: product, a Fraction, containing the product of the numerators and denominators of the two operands.

Do this...
Write a definition of Fraction#multiply(Fraction) given the definition, specification, and example above. Write out the OCD if you get stuck.

Do this...
Write a helper method FractionTest#assertFraction(String,Fraction,Fraction) which works similar to CoordinateTest#assertCoordinate(String,Coordinate,Coordinate).

Do this...
Write another test method, FractionTest#testMultiply(). Write assertions in this method (using the new helper method) to test these multiplications:

1/2 * 3/4 = 3/8
-2/3 * 10/7 = -20/21

Both factors in both equations are fractions you should already have in instance variables in your in FractionTest.

Do this...
Compile your code, and run for a green bar. Also, uncomment the last two commented lines of code in the PierreCLIDriver class, and run it, too.

Fraction Simplification

Mathematically speaking, all of these fractions are the same value: 1/2, 2/4, 3/6, 4/8, 5/10, etc. However, with respect to Java, a Fraction object storing 1/2 is different from a Fraction object storing 2/4; the internal data is different.

We also have to worry about negative values: mathematically, -1/2 == 1/-2 and 1/2 == -1/-2.

What we want is a canonical form for fractions:

The question is when and where do you want to do this? You want to simplify every fraction, but you don't want to write the code for this too many times. Consider that you could define addition, subtraction, division, negation, and six different kinds of comparison operations. Do you want to have to remember to simplify in each of these methods? No, you don't.

Truth be told, canonical forms are just a special kind of invariant. Invariants have to be tested when new objects are created (i.e., in constructors) or when instance variables are changed (e.g., in mutators, see below).

First, you need a simplify() method. Think of this as your canonical-form enforcer.

Behavior of Fraction#simplify()

I am a fraction. When I need to be simplified, I divide my numerator and my denominator by the largest number that divides them both. Also, if my denominator is negative, I negate both my numerator and my denominator.

From gradeschool, you might remember this "largest number that divides them both" being called the greatest common divisor (GCD).

Notice that no information goes in or out of this method. That means no parameters and nothing returned! I manipulate my own numerator and denominator.

Objects of Fraction#simplify()
Description Type Kind Movement Name
my numerator int instance variable instance myNumerator
my denominator int instance variable instance myDenominator
the GCD int varying local gcd
Operations of Fraction#simplify()
Description Predefined? Name Library
compute the GCD no computeGCD Fraction
divide integers yes / built-in
negate a variable yes *= -1 built-in
Algorithm of Fraction#simplify()
  1. Let gcd be the greatest common divisor of myNumerator and myDenominator.
  2. If gcd != 0, then
    1. Set myNumerator equal to myNumerator divided by gcd.
    2. Set myDenominator equal to myDenominator divided by gcd.
    End if.
  3. If myDenominator is negative, then
    1. Negate myNumerator.
    2. Negate myDenominator.
    End if.

The last step uses a bit of a trick for keeping the denominator always positive. Consider what happens:

Original Simplified
1/2 1/2
-1/2 -1/2
1/-2 -1/2
-1/-2 1/2

This is exactly what we want.

Notice that after this method has finished, the state of the object may have changed (i.e., myNumerator and myDenominator may be different). A method that changes the state of the object is called a mutator. One common tell-tale sign of a mutator is that the method doesn't return anything---a void return type. That's exactly what happens here: I'm a Fraction object, and I've modified the contents of my instance variables, so what would I have to share with the rest of the world? I've changed, so I don't have to return any data.

You'll need this method in your Fraction class:

  /**
   * This method finds the greatest common divisor of two integers,
   * using Euclid's (recursive) algorithm
   * @param alpha one integer.
   * @param beta the other integer.
   * @return the greatest common divisor of alpha and beta.
   */
  private int computeGCD(int alpha, int beta) {
    alpha = Math.abs(alpha);
    beta = Math.abs(beta);
    if (beta == 0)
      return alpha;
    else
      return computeGCD(beta, alpha % beta);
  }

It even has the Javadoc comments for you. (It also has private visibility which is allowed; we use it here because no one else really needs to know about this method outside the class.)

Do this...
Add the computeGCD() method to your Fraction class.

Negating an integer is just a matter of multiplying by negative one:

myNumerator *= -1;

Do this...
Define the method Fraction#simplify() using the algorithm above, give it some Javadoc comments, and compile your code. Make sure you conform to the method specification described above (no parameters, no returned value)

The method does you no good, though, until you call it. But where, when, and how?

Where? The discussion above suggested that a fraction should be simplified whenever it is created.

When? Simplifying a fraction is the last step in constructing a fraction.

But how? It's a method, and you've seen the pattern for invoking methods, however, simplify() is an instance method, and you need to invoke it on yourself.

Think about this in internal perspective: "I am a Fraction object, and... blah, blah, blah... In the end, I simplify myself." What name do "I" have? Parameters have names, but the current instance doesn't have an obvious name. Truth be told, this "me" instance does have a name, but we don't need it. Invoking an instance method on yourself can be done by omitting the instance and the period.

It looks a little strange at first, perhaps, but keep in mind that with an internal perspective, you're basically talking to yourself. In fact, you're ordering yourself around: "Hey, me, go simplify myself!" Maybe the syntax is appropriately strange!

If I had a Coordinate#simplify(), here's what it'd look like in my default contructor:

public Coordinate() {
  myX = 0.0;
  myY = 0.0;
  simplify();
}

As a coordinate object, I set my x and y coordinates to default values, and then I simplify myself.

Do this...
Invoke the Fraction#simplify() method at the end of the explicit-value constructor.

Question #6.05 Why don't you have to call simplify() at the end of the default constructor? You can, but it's not necessary.
To be answered in Exercise Questions/lab06.txt.

Testing Simplification

There are a variety of ways we could test the simplify() method. We could test it in testToString(), testMultiply(), and any other test method that tests a computation and uses simplify(). In fact, you must go back to those methods and make sure that you account for simplified results.

However, we should be a little more deliberate about testing the simplify() method itself. It doesn't hurt if we incidentally test it through the other computation methods, but we should dedicate one test method just to the simplification problem.

Do this...
Add a new test method code testSimplify() to FractionTest.

You won't invoke simplify() directly since you can count on the explicit-value constructor calling it.

Do this...
Start out by testing the negation possibilities. Look at the table above to simplify negative fractions. Here's what one of those assertions might look like:

assertFraction("numerator and denominator negative", new Fraction(1, 2),
    new Fraction(-1, -2));

This might seem a little odd since the computation is so simple, but it's actually testing quite a lot!

Do this...
Now test the common-factor simplification. These are up to you. Come up with at least three fractions that aren't simplified, and test their simplifications in a similar way. At least one test should have a numerator greater than a denominator. At least one test should trigger both the GCD simplification and have a negative denominator.

Do this...
Add a test to testMultiply() that generates a product that should be simplified. This is to make sure that simplification is done by the multiply() method. You can reuse some of the earlier fractions to demonstrate this.

Object-Centered Design Revisited

Now that you have seen how to build a class, we need to expand our design methodology to incorporate classes:

  1. Describe the behavior of the program.
  2. Identify the objects in the problem:
    If an object cannot be directly represented using available types, design and implement a class to represent such objects.
  3. Identify the operations needed to solve the problem:
    If an operation is not predefined, design and implement a method for the object type that is responsible for the operation.
  4. Organize the objects and operations into an algorithm.

Using this methodology and the Java class mechanism, we can now create a software model of any object! (The rest of your computer programming education is about learning the most efficient and effective ways of doing this.) The class mechanism thus provides the foundation for object-oriented programming (OOP), and mastering the use of classes is essential for anyone wishing to program in the object-oriented world.

Submit

Submit copies of all your code plus a sample execution of your test-case class and five executions of your driver.

Question #6.06 Did you remember to record the total amount of time it took you to complete this lab in Question #6.02?
To be answered in Exercise Questions/lab06.txt.

Terminology

accessor, canonical form, class, construct an object, constructor, data type, default constructor, denominator, explicit-value constructor, greatest common divisor (GCD), instance variable, internal perspective, invariant, invoke a method, local variable, message, mutator, numerator, object attribute, object operation, object-oriented programming, OOP, overloading, parameter, postcondition, private visibility, public visibility, visibility