CIS300 Spring 2005

Software development in Java: classes, objects, and packages

In the previous lecture, we learned that programs are designed with three components: model and view and controller.

The Java language is a bit confusing, because there are two notions for ``component'' --- class and object. Here is a simplistic explanation:

  1. The program pieces we write are classes. Each class lives in a file on disk.
  2. When we start a program, copies of the classes are created in the computer's primary storage. The copy in storage is an object. It is possible to copy a class into primary storage more than once, thus constructing multiple objects from the same class. (Example: You write a class LibraryBook, and then you start a program that constructs 100 new LibraryBook(...) objects.)
  3. When we start a program, say
    java LibraryManager
    
    this constructs a LibraryManager object from class LibraryManager. Within LibraryManager, there might be statements of the form,
     ...  new LibraryBook(...)  ...
    
    The new statement constructs in storage a LibraryBook object from class LibraryBook. In this way, multiple objects are constructed and live in primary storage.
  4. Before a program starts, there already exist several prebuilt objects, such as System.in and System.out. A program can use these, say, as the view component(s) of the application.

To summarize: classes are the components that the programmer writes and saves on disk; objects are the components that live in primary storage when the program executes. If we wish to be pedantic, we say that the component diagrams in the previous lecture are object diagrams.

Software development in Java

Recall the software development methodology stated at the end of the previous lecture:
  1. Requirements: The program's intended user tells us how she wishes to use the program. The user must tell us stories and draw pictures that explain the different ways the program might be used. Each possible usage is called a use-case.
  2. Design: The programmer studies the use-cases and applies the knowledge to designing the component structure, data structure, and control structure of the solution. Once the program is designed, the programmer does a ``safety check,'' explaining how each use-case executes with the design.
  3. Implementation and Testing: The program is written to match the design, and it is tested to verify that it behaves correctly. The testing usually proceeds in two stages: unit testing, where each component is tested by itself as much as possible, and integration testing, where the entire assembled program is tested on the use-caes.
Let's study a simple example.

Case Study: Counting votes

You are the Diebold Company, and the state of Ohio has asked you to write the software for their electronic voting machines for the 2004 US presidential election.

I. Requirements

You begin by asking the Ohio election officials to give you examples --- use-cases --- that describe how the software should behave. They might give three such use-cases:

The set of use-cases is almost always incomplete, and the program designer might propose more of them, e.g., ``What happens if the voter types kerry or kery instead of Kerry''? Rarely does a programmer begin with ``all'' the use-cases, but there should be enough of them to gain a good understanding of the problem.

II. Design

We now apply the design stages described in the previous lecture.

Component structure

The vote-counting program fits the usual model-view-controller structure:

The first and most important task is to decide the data structure that resides in the model.

Data structure

The program's job is to total votes, so a table (array) is needed, where each cell of the array holds the total for one candidate. Here is a picture:

The programmer might ask: ``Are these all the candidates?'' ``Does Ohio allow write-in ballots?'' The answers to these questions affect the data structure. For now, we work with the 3-celled array.

Control structure

The controller holds the algorithm that controls vote counting. Simply stated, the algorithm goes
1. read and total the votes
2. print the totals
We also know that Step 1 must be conducted one vote at a time, so the details for Step 1 go something like this:
1a. read a vote (if there is one to read!  if not, quit Step 1)
1b. if the vote is for Bush (or Kerry or Nader), add one to the appropriate count
1c. repeat Steps 1a and 1b until no more votes to read
The outline should list all the actions the program must do, even if we do not state the details precisely. Now we are ready to draw the algorithm as a flow chart, which is a graph that shows the details and ordering. Here is the flow chart for Steps 1a and 1b:

Now, we finish the flow chart by including Steps 1c and 2. This adds a loop --- a backwards arc --- to the flowchart and gives us the following:

When we take a close look at the algorithm, we notice that the phrase, ``get input,'' which suggests that we should use a method in the view component to read the vote. Next, the phrase, ''add one to Bush's count,'' suggests we should make a method in the model component to add a vote for Bush (similarly for Kerry and for Nader). Finally, the phrase, ``print the totals'', suggests there should be a method that gets the totals (and a method in the output view to print them).

This suggests we modify the model with some extra methods:


Input-output structure

Whenever possible, we should use pre-built components to handle input and output. Since the votes will arrive from an input dialog, we can use the dialog object that is prebuilt into the Java libraries --- It is javax.swing.JOptionPane. The output can be printed into a command window, using the object, System.out.

Summary of design

Here is the current picture of our design:

The red dashed arrows are added to remind us which steps in the controller's algorithm will invoke the methods in the model and the view. The purple solid arrows are traditional; they summarize the red dashed arrows by telling us which components depend on the other components to execute. (Here, the controller cannot execute without its model and view, but the model can be extracted and used without the controller and view, say, in another application.)

Now it is time to write the codings of the methods and the coding of the algorithm.

III. Implementation

It is best to code the model first, because any flaws in the program's data structures will render the program useless. Based on our design, here is a coding of the model:

package Election2004;
/** VoteTable remembers the votes for the 2004 US Presidential Election */
public class VoteTable
{
  private int[] vote; // holds the votes for the candidates
                      // vote[0] holds Bush's votes
                      // vote[1] holds Kerry's votes
                      // vote[2] holds Nader's votes

  /** Constructor  VoteTable  initializes the table */
  public VoteTable()
  { vote = new int[3]; }  // all cells are automatically set to 0

  /** voteForBush  adds one more vote to Bush's total */
  public void voteForBush()
  { vote[0] = vote[0] + 1; }

  /** voteForKerry adds one more vote to Kerry's total */
  public void voteForKerry()
  { vote[1] = vote[1] + 1; }

  /** voteForNader  adds one more vote to Nader's total */
  public void voteForNader()
  { vote[2] = vote[2] + 1; }

  /** computeTotals reports the votes for the three candidates
    * @return a string that lists the candidates and their votes. */
  public String computeTotals()
  { return   "Bush = " + vote[0] + "\n"
           + "Kerry = " + vote[1] + "\n"
           + "Nader= " + vote[2] + "\n" ; }
}
This component should be tested by itself (called unit testing) as thoroughly as possible. You can use a Development Environment (e.g., BlueJ or Eclipse) to do this, or you can write a tester program, sometimes called a test harness, to do this. Here is a sample test harness:
package Election2004;
public class TestModel
{ public static void main(String[] a)
  { VoteTable t = new VoteTable();

    System.out.println(t.computeTotals());  // all candidates should have 0s

    t.voteForNader(); 
    System.out.println(t.computeTotals());  // Nader should have 1

    t.voteForKerry();
    System.out.println(t.computeTotals());  // Nader and Kerry should have 1

    // etc.
  }
}
A test harness should test all the methods of the component, invoked in all possible orderings.

Next, here is the controller, named Start:

package Election2004;
import javax.swing.*;
/** Start starts and controls the vote counting. */
public class Start
{ public static void main(String[] a)
  {
    VoteTable table = new VoteTable();   // construct model object

    // tabulate the votes:
    boolean processing = true;
    while ( processing )
          { String s = JOptionPane.showInputDialog(
	                 "Please type the last name of your candidate:");
            if ( s == null )  // did election official press Cancel ?
                 { processing = false; }  // yes, time to quit
            else { if ( s.equals("Bush") )
	              { table.voteForBush(); }
                   else if ( s.equals("Kerry") )
		      { table.voteForKerry(); }
                   else if ( s.equals("Nader") )
		      { table.voteForNader(); }
		   else { } // it's an invalid vote and is lost forever...
                 }
           }
     // total the votes:
     System.out.println( table.computeTotals() );
     System.exit(0);  // terminate program (including graphics components)
  }
}
It is a bit difficult to do unit testing on a controller, since it controls the other parts of the program, but it is possible to write a dummy version of VoteTable for testing the controller. The dummy version, called a stub (sorry --- it's a bad name), might look like this:
package Election2004;
/** This is a dummy VoteTable, for testing purposes only. */
public class VoteTable
{ public VoteTable() { }

  public void voteForBush() { System.out.println("B"); }
  public void voteForKerry() { System.out.println("K"); }
  public void voteForNader() { System.out.println("N"); }

  public String computeTotals()
  { return "request to compute totals" }
}
There is no intelligence in the dummy version; it is built to let us see the order in which the controller uses the methods in the model.

Finally, note that we have used the prebuilt objects, javax.swing.JOptionPane (for dialog input) and System.out (for output to the command window), so there is no additional programming needed for the view components.

Now, the application is ready for systems testing and ultimately, to let the voters of Ohio decide the 2004 US presidential election.

Java Packages

Perhaps you notice that the two components in the case study were prefixed by the line,

package Election2004;
This tells the Java compiler that the two classes ``belong together'', and indeed, they must be placed in the same disk folder (directory), named Election2004. Such a folder is called a Java package.

Java makes it easy to create and execute a package.

(Note: If you use the BlueJ IDE, you should read the CIS300 web pages, Constructing packages within BlueJ and How to use BlueJ to start a main method to learn how BlueJ lets you build and execute packages. The notes there might be a bit out of date, but if you are having problems making packages with BlueJ, the notes might be useful.)

The following instructions work with the Java Development Kit and a command window: Say that we want to group the above two classes into a package named Election2004:

  1. We create a new folder, named Election2004.
  2. We insert class Start and class VoteTable into the folder.
  3. As the first line of both of the two classes, we insert:
    package Election2004;
    
We must recompile the classes:
  1. Close (exit) folder Election2004.
  2. Compile each class, e.g.,
    javac Election2004\VoteTable.java
    
    (or, if that doesn't work, use)
    javac -classpath . Election2004\VoteTable.java
    
    (The -classpath . tells the Java compiler that the other classes in the package are found where VoteTable.java lives. ) Note the folder name prefixed to the class name.
We execute the start-up class of the package in a similar way:
java Election2004.Start
Note that the folder name is prefixed with a dot to the start-up class name.

We can generate an interlinked collection of web pages for the package with just one javadoc command:

javadoc Election2004

You can see the Election2004 package at http://www.cis.ksu.edu/~schmidt/300s05/Lectures/VoteExample .

As an exercise, you should copy the two classes to your computer, make a package, compile it, execute it, and use javadoc to generate its API specification.

There are several advantages to grouping programs into packages:

  1. Program components that work together are grouped together --- we won't lose or forget a component.
  2. Packages can be saved in ``libaries'' and reused. Indeed, all the graphics we do with Java programs reuses packages others wrote for us.
  3. The javadoc program can be used to generate a family of cross-indexed web pages that document all the components in a package. The use of javadoc makes it possible to quickly document and redocument systems during design and development.
All three items listed above are important. We have already exploited Item 2 each time we wrote a program using a graphical user interface --- when we added
import javax.swing.*;
at the beginning of the classes we wrote, we were connecting to and using a package, namely javax.swing, that someone else wrote.

Indeed, the general pattern for using an already written package, P, in an application is to

This makes the components in the package available for use.