CSC 207 | Grinnell College | Spring, 2012 |
Algorithms and Object-Oriented Design | ||
This reading explores an extended example (a directory of students, faculty, and staff) to introduce how related classes may be defined and used within a hierarchy of class definitions. The resulting structure illustrates a powerful problem-solving mechanism.
Consider the problem of maintaining a directory of students, staff, and faculty. For the purposes of this lab, suppose an entry in the directory has the following fields:
All Entries
Students
Staff
Faculty
The directory should contain these methods:
This problem illustrates some common characteristics of a variety of applications. At one level, we want to consider all people in the directory as being similar -- the directory should be a collection of entries. At another level, each type of person (e.g., students, staff, faculty) has special characteristics.
One way to accommodate such multiple levels is develop an appropriate class hierarchy. In this case, we might begin with an Entry class and then define three subclasses, Student, Staff and Faculty. Schematically, this is shown in the following diagram:
Noting that both Staff and Faculty have offices and office extensions, this hierarchy might be refined further, as follows:
For an extended example, this alternative hierarchy might have several advantages as more common characteristics of staff and faculty were identified. For simplicity in what follows, however, we adopt the first of these class hierarchies, focusing on classes Entry, Student, and Faculty for individuals and class SchoolDirectory for the overall structure. The development of the Staff class is left as an exercise.
The Entry class should contain the data and capabilities that we might want for the records of all types of people. In reviewing the problem, some obvious methods involve the creation of an entry, converting the class to a string (for printing), and checking if an entry's name matches a given name. In addition, if names are to be stored alphabetically, it will be convenient to be able to compare if one entry equals another or if one entry comes before another alphabetically. Program Entry.java contains a simple version of such an Entry class.
An object or a class is a self-contained entity, and it should include those data elements and operations that naturally support that entity. For the Entry class, we identify both data and operations which seem closely related to each other and to our image of what directory entries might contain.
As you review this Entry class, note that it contains its own testing code in its main method. When this code is run by itself, this main method will run to provide appropriate testing. When Entry is imported and another class is run, then that class's main will provide the basis for program execution.
With Entry defined, we can define the Student class by extending Entry and adding only the few new required fields. The resulting code is Student.java
For the most part, the code in Student.java is straightforward. However, in two places, we want to utilize existing code in super class Entry as a first step in later processing. Specifically, in initializing a Student, we first call upon Entry's constructor to initial the inherited fields, firstName, lastName, and eAddress. Such a reference to a method in a super class uses the keyword super. Similarly, a reference to the toString method in the super class Entry is given by super.toString.
Program Faculty.java provides an implementation of the Faculty class. As with Student, we take advantage of the Entry super class when we can, although some new or adjusted methods are needed.
Before writing the SchoolDirectory class, we must decide how to store entries. As suggested earlier, one approach is to keep the entries, arranged alphabetically by name, in an array -- but what size should the array be? One approach is to create an array of some size maxSize and maintain a separate variable currentSize. We can keep adding entries to the array until currentSize equals maxSize. At this stage, we could generate a larger array (perhaps twice the size), copy the old array to the new, larger one, and then make the insertion. Such an approach does not place an arbitrary limit on the directory size, but also does not waste space excessively.
With this approach to array expansion, we can insert new elements in name order, following the insertion process from an insertion sort. Printing can follow a linear scan of the array, and data retrieval can follow from a binary search. For data retrieval, however, we still must decide what the search should return. A common approach would yield the entry in question, so the program could do subsequent processing if desired. All of this work follows algorithms and approaches seen previously in the course, and we can present a first version of program SchoolDirectory.java.
While the coding within SchoolDirectory.java follows directly, the resulting program illustrates several important points:
Method add depends upon both Student and Faculty being subclasses of Entry. In particular, the parameter person for add must be an Entry. Through inheritance, every Student and Faculty object is just a special case of an Entry object, so this requirement holds. Inheritance sometimes is said to reflect an is-a relationship -- every Student is-a Entry, so we can use a Student object anywhere the code requires an Entry object.
Further, add only relies on methods guaranteed to exist for an Entry, so the logic works correctly for add -- regardless of whether the person is just an Entry or a special Entry (e.g., a Student or Faculty).
Inheritance identifies methods that are available for the class and any subclass, so objects of different subclasses may be used in a broad framework.
In Java, all classes are considered subclasses of a special class Object. The Object class provides a few basic operations, such as a default constructor. In addition, Object contains a toString method, which returns a string representing some data in an object. Until this lab, we have not used this method explicitly, although it has been implicit in many of our System.out.print statements. For example, consider the statement
System.out.print("The output is" + data);
As print ultimately requires a string for output, the Java compiler must find a mechanism to find a printable string to represent the data object. Java accomplishes this task by invoking the toString method inherited from Object. While the details of this method may change from class to class, toString always exists in some form for any class.
In object-oriented problem solving, the interpretation of the method by an object as the code is running is called polymorphism. With polymorphism, the program can identify the name of a method when the program is written and compiled. However, the details of the method are not decided until the program is actually run, as the specific class for the method may change from one run to the next. Polymorphism provides the mechanism for the details of this method to be interpreted when needed during execution. For this application, we have defined our own toString method for each Entry class and subclass, so the printing of each entry will look appropriate.
SchoolDirectory's method print takes advantage of polymorphism and may be deceptively simple. As with add, print just cycles through a sequence of entries and prints them. However, details for printing for a Student is not the same as those for a Faculty. By writing System.out.print(entryArray[i]), we employ the toString message to each Entry object or subclass object. The System.out.print method needs a String representation for each entry, so it implicitly calls the toString method for each entryArray[i] object. The entryArray[i] object then interprets the message according to what class it is. Students use their toString method, while Faculty use theirs.
While class SchoolDirectory worked reasonably well, the lookup method contains some awkwardness. In particular, the method is supposed to return a specified Entry. If the person is found, the method works as desired. The difficulty arises if the person is not present.
In SchoolDirectory.java, lookup resorts to returning a null value if no such person is found. While this approach satisfies the specification of the method by yielding a special value, this approach can cause additional effort in an application, as shown in the corresponding main method. Specifically, lookup always returns something, and that object must be tested each time to determine if the returned value is null.
A cleaner approach is to break the normal flow of processing, using an exception. In general, an exception is any event that may require a change in the normal flow of processing. In Java, a program "throws" an exception when the unusual event is detected. Statements in the block calling the method then "catch" the exception, allowing appropriate action to be taken. To illustrate this approach for the SchoolDirectory example, program SchoolDirectoryAlt.java takes advantage of a NoSuchElementException, already built into Java in the java.util class.
In this revised program, the return null statement at the end of the lookup method is replaced by
// if person not found, generate an exception throw new NoSuchElementException();
With this addition, normal processing is interrupted if the desired name is not found. Throwing the exception allows the end of the main method to be much cleaner. The main structure for this code is
try { . . . dir.lookup("- - - -", "- - - -"); . . . } catch (NoSuchElementException e) { System.out.println("Directory Entry not found: "); System.out.println(" Exception caught: " + e); }
In this try ... catch block, the try keyword indicates that processing should shift to the catch section if any exceptions are encountered in the block. The block itself then can be considerably simpler.
More generally, a program can contain several try ... catch blocks (even one for each lookup), and exceptions can be handled differently in each one if desired.
This document is available on the World Wide Web as
http://www.walker.cs.grinnell.edu/courses/207.sp12/readings/reading-generalization.shtml
created 2 May 2000 by Henry M. Walker revised 27 January 2012 revised to eliminate references to PrintWriter, update entry 4 February 2012 |
![]() ![]() |
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu. |