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.

Write this information down, along with your name and some version information, in the form of a Java comment. If you follow the javadoc conventions, it'll be easier to generate user-friendly documentation later. For example,

 /**
     Student: represents a student, with contact information and grades.

     @author Stephen Bloch
     @version Jan. 19, 2001
  */
 

List desired 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).
List the properties of this class, along with their types and other information (e.g. what units are they measured in, what values are legal/illegal, etc.). In the next step, they'll turn into instance variables.

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). In the next step, they'll turn into methods.

Define the interface
Describe these services more precisely in Java syntax. Typically, each "behavior" will become a method in the class. This is a good time to write the contract, header, and examples for each of the methods.

Since you also decided, above, what information the class contains, you can now put this decision into Java syntax by writing instance variable declarations, e.g.

    /**
       name: the student's name. May contain any non-empty string of characters.
     */
    private String name;
 
Note that javadoc won't display these comments if the variable is declared private, but I'll insist that the information be there in any case.

Write a class definition skeleton
This is just the standard syntax for a class, together with "placeholder" comments to remind yourself of where you're going to put the various methods and instance variables:

 /**
     Student: represents a student, with contact information and grades

     @author Stephen Bloch
     @version Jan. 19, 2001
  */
  class Student
     {
     // instance variable(s)
     /**
	name: the student's name.  May contain any non-empty string of characters.
      */
     private String name;
  
     // constructor(s)
  
     // access method(s)
   
     // I/O and conversion method(s)
  
     // interesting method(s)
  
     }
 

For each of the methods you defined in the previous step, categorize it and put its contract, header, and examples into the appropriate place, e.g.

 /**
     Student: represents a student, with contact information and grades

     @author Stephen Bloch
     @version Jan. 19, 2001
  */
  class Student
     {
     // instance variable(s)
     /**
	name: the student's name.  May contain any non-empty string of characters.
      */
     private String name;
  
     // constructor(s)
     /**
	Standard constructor, taking a String.
	Example: new Student ("Joe Schmoe") produces a Student with name
	"Joe Schmoe".

	@param theName	the name to give the new student
      */
     public Student (String theName) {
	...
	}
  
     // access method(s)
     /**
	Get the student's name.

	@return	a String for the student's name
      */
     public String getName () {
	...
	}
   
     // I/O and conversion method(s)
  
     // interesting method(s)
  
     }
 

Write the methods
Writing methods is the part that takes the most thought, but it's not too bad if you've already done all of the above and you're not asking one method to do too much work. Follow the design recipe for Java methods.

I recommend writing method contracts, headers, and examples for all the methods that you defined above, then picking one method at a time for filling in templates, filling in bodies, and testing. Choose methods in a "bottom-up" fashion: start with one that doesn't depend on any others, then one that depends on the first, etc.

I also recommend writing, before any other methods (or at least fairly early), a testing method:

 /**
    test: where we put all the standard test cases for the class
  */
 public static void test () {
    System.out.println ("Testing class-name.");
    System.out.println ();
    }
 
This doesn't do anything interesting yet (except tell you what class you're working on), but as you write each new method, you'll add its test cases to the body of the test method, and you can see whether it works simply by invoking the test method.
Alternatively, you can try individual test cases (in BlueJ or DrJava) by hand, but this is less and less convenient as the number of methods and test cases grows.

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: Fri Jan 19 11:35:10 EST 2001
Stephen Bloch / sbloch@adelphi.edu