We are now ready to define a simple program to sum the integers in a lists. We will add a definition of the method
int sum();to each class in our composite class hierarchy for IntList. Let's begin by writing the contract and header for sum in the abstract class IntList:
// IntList := Empty() + Cons(Object, IntList) abstract class IntList { ... abstract int sum(); // returns the sum of the numbers in this }
Next we need to generate examples showing the expected behavior of the method:
/** IntList := Empty() + Cons(Object, IntList) */ abstract class IntList { ... abstract int sum(); // returns the sum of the numbers in this } class IntListTest() { static final oneElt = new Cons(1, Empty.ONLY); static final twoElts = new Cons(5, oneElt); static final threeElts = new Cons(-10, twoElts); /** Given the the answer ans, computes the sum() on this and * compares it against ans. If the values disagree, it throws an * exception with an embedded error message; otherwise it * silently returns. */ void test(IntList input, int ans) { int result = sum(); if (result != ans) throw new RuntimeException("Test FAILURE: computed answer is: " + result + " correct answer is: " + ans); } void sumTest() { test(Empty.ONLY, 0); test(oneElt, 1); test(twoElts, 6); test(threeElts, -4) } }
The test and sumTest methods both have result type void, which is the degenerate, empty type. There are no values of type void. A method with void return type does not return a value. When running a program test, we want the test to ``silently'' return (indicating successful completion) unless there is an error, which generates an exception aborting the execution of the test.
As the fourth step, we select a template for writing the sum method, which for methods on composite hiearchies like IntList is usually the interpreter pattern:
class Empty { ... int sum() { ... } } class Cons extends IntList { int first; IntList rest; ... int sum() { ... first ... rest.sum() ... ; } }
Finally, we complete the coding process by filling in the bodies of the methods in the template:
class Empty { ... int sum() { return 0; } } class Cons extends IntList { int first; IntList rest; ... int sum() { return first + rest.sum(); } }
To finish the design recipe, we test our code using the examples in the main method of IntList.
Finger Exercise: Load your saved file IntList.java into
DrJava Definitions pane. Define the sum method as described
above. Try some simple tests in the interactions
pane to convince yourself that your IntList class (including
the sum method is correctly coded.
Next add the IntListTest class to your program in the Definitions pane. In the Interactions pane, run the tests specified in the method invocation new IntListTest().sumTest(). The method should silently return if you coded everything correctly.
Using the same design recipe, add a definition of a method prod to compute the product of a list of numbers and test it. Note that the Data Analysis and Design step has already been done in the definition of the IntList hierarchy. Save your revised program in the file IntList.java.