Design Recipe for Java Classes

Define the problem

State, clearly and unambiguously in English, what the class is supposed to do or represent. If you can't do this in English, you have no hope of doing it in Java.

For example,

Represent a student, with identifying information and grades.

Identify properties and behaviors
A property is a piece of information about class instances that satisfies one or both of the following requirements:
  1. It can be different from one class instance to another (e.g. name: each Student may have a different name)
  2. It can conceivably change over time (e.g. age or gradePointAverage).

A behavior is something a class instance can do, or something that can be done to a class instance. Typically, behaviors are carried out the same way for all instances of a given class, and this way doesn't change over time, but it may depend on the values of properties (above).

For example,

Represent a student, with identifying information and grades.
Properties: first and last names, student ID number, and GPA.
Behaviors: A user should be able to create a Student with specified name, ID, and GPA; to retrieve any of this information about an existing Student; and change the GPA.

In the case of a complicated class, there may be some parts that you don't want to tackle immediately; write down something about them anyway, and put them on a list of versions, e.g.

Version 1:
Student class with basic instance variables, access methods, and toString()
Version 2:
add the ability to hold a bunch of grades in one Student; keep track of how many there are; add a new grade; change an existing grade; and compute the average of the existing grades
Version 3:
add a transcript that keeps track of what courses the student has already taken, and what grades were earned in each
Version 4:
add the ability to check prerequisites to see whether a student has already taken the prerequisites for a proposed course
etc.

Choose names and types

for the class, its properties, and its behaviors. Typically, each property becomes an instance variable, and each behavior becomes a method. This is a good time to write down other relevant information (e.g. in what units is a variable measured, what values are legal/illegal, etc.).

For example,

Represent a student, with identifying information and grades.
Properties: first and last names, student ID number, and GPA.
Behaviors: A user should be able to create a Student with specified name, ID, and GPA; to retrieve any of this information about an existing Student; and change the GPA.
Class name: Student
Instance variables: firstName and lastName of type String, id of type int, and gpa of type double.
Methods: A constructor taking in firstName, lastName, id, gpa in that order; accessor methods getFirstName(), getLastName(), getID(), getGPA(), and setGPA(double newGPA); standard methods toString() and equals(Object other).

Write test cases

Before you write any Java code, figure out how you're going to test your class. For example, if you wanted to test the Student class described above, you might say

    Student joe = new Student("Joe", "Schmoe", 1479025, 3.2);
    joe.getFirstName() // should be "Joe"
    joe.getLastName() // should be "Schmoe"
    joe.getID() // should be 1479025
    joe.getGPA() // should be approximately 3.2
    joe.toString() // should be "Joe Schmoe (1479025), GPA 3.2"
    joe.setGPA(3.3)
    joe.getGPA() // should be approximately 3.3 now
    joe.toString() // should be "Joe Schmoe (1470925), GPA 3.3"
    
Write Java code

You'll probably want to do this in several small steps. For example, you might

  1. start with an empty class definition, with its javadoc comment:

     /**
       * Represents a student, with contact information and grades.
       *
       * @author Stephen Bloch
       * @version 1, Feb. 23, 2004
       */
        class Student {
        }
     

  2. add instance variables:

     /**
       * Represents a student, with contact information and grades.
       *
       * @author Stephen Bloch
       * @version 2, Feb. 23, 2004
       */
        class Student {
            /** Student's first and last names.
    	  * @since version 2, Feb. 23, 2004
    	  */
    	private String firstName, lastName;
            /** Student's student ID number.
    	  * @since version 2, Feb. 23, 2004
    	  */
    	private int id;
            /** Student's current GPA.
    	  * @since version 2, Feb. 23, 2004
    	  */
    	private double gpa;
        }
     

  3. Set up a testing infrastructure. This may be a public static void test() method in the class, or it may be a separate class extending JUnit's TestCase class, or even just a list of method calls to be invoked interactively (if you're using BlueJ, DrJava, or ProfessorJ). Each of these approaches has its advantages and disadvantages: the first approach allows you to test private methods, the second allows you to take advantage of IDE support for running JUnit test cases and showing you which ones passed and which failed, and the third is the quickest (but needs to be re-typed every time you modify the class, to make sure you haven't broken something that used to work). In any case, this is where you'll put your test cases for each method, as described in the design recipe for methods.

  4. For each method, go through the design recipe for methods. Be sure to do this in order: first the methods that don't depend on any others, then methods that depend on those, and so on, so you can test each individual method before going on to the next. Also, if some methods aren't needed at all until, say, version 3 of the class, you can postpone writing them until after you've tested and debugged versions 1 and 2 of the whole class.

Test the class
In the course of the design recipe for methods, you've already tested each of the methods individually. But you're not finished: it is possible that some non-obvious interaction among the methods causes incorrect behavior. So now you need to test the class as a whole.

You should already have lines in your static void test() method that create a number of objects of the class, with different properties, try various individual methods on them, and print the results. Now start trying different sequences or combinations of operations on each object. If each such sequence of operations behaves as expected, you have reason to believe the class is correct. If not, you have a defect somewhere: you already have confidence in each method on its own, so the defect must be an interaction or misunderstanding between two or more methods (for example, there's a housekeeping chore to be done, and each method thinks the other is doing it so it doesn't get done, or both methods do it so it gets done twice).


Last modified: Mon Jun 13 20:20:34 EDT 2005
Stephen Bloch / sbloch@adelphi.edu