A School Directory as an Example of Object-Oriented Design

Summary

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.

A Student, Staff, and Faculty Directory

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:

The directory should contain these methods:

Some Observations

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:

a class hierarchy

Noting that both Staff and Faculty have offices and office extensions, this hierarchy might be refined further, as follows:

a refinement for the Staff and Faculty classes

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

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.cpp contains a simple version of such an Entry class.

Design Principle

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.

The Student Subclass

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.cpp

For the most part, the code in Student.cpp 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.

The Faculty Subclass

Program Faculty.cpp 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.

The School Directory Class

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.cpp.

Inheritance, is-a Relationships, and Polymorphism

While the coding within SchoolDirectory.cpp follows directly, the resulting program illustrates several important points:

Exceptions and Exception Handling

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.cpp, 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.cpp 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
translated to C++ 9 January 2023
Valid HTML 4.01! Valid CSS!