Previous Section
 < Free Open Study > 
Next Section


Case Study

Real Estate Listings: An Object-Oriented Design

Problem Write a program to keep track of a real estate company's residential listings. The program needs to input and keep track of all listing information, which is currently stored on 3" × 5" cards in a box in the firm's office.

The real estate salespeople must be able to perform a number of tasks using these data: add or delete a house listing, print the information about a particular house given the owner's name, and print a list of homeowners sorted alphabetically.

Brainstorming We said that nouns in the problem statement represent objects and that verbs describe actions. Let's approach this problem by analyzing the problem statement in terms of nouns and verbs. Let's circle nouns and underline verbs. The relevant nouns in the first paragraph are listings, information, cards, box, and office: Circle them. The verbs that describe possible program actions are keep track of, input, and stored: Underline them. In the second paragraph, the nouns are salespeople, listing, data, information, house, name, list, and homeowners: Circle them. Possible action verbs are perform, add, delete, and print: Underline them. Figure 3.12 shows the problem statement with the nouns circled and the verbs underlined.

Click To expand
Figure 3.12: Problem statement with nouns circled and verbs underlined

We did not circle program or underline write because these instructions apply to the programmer and are not part of the problem to be solved. Thus our initial list of classes contains the following: listings, information, cards, box, office, salespeople, data, house, name, and homeowners. We'll shorten salespeople to people and homeowners to owners. Now, let's examine these nouns and see which actually represent classes in our solution.

Filtering The first paragraph describes the current system: The objects are cards that contain information. These cards are stored in a box. Therefore, we have two objects in the office to simulate: 3" × 5" cards and a box to hold them. In the second paragraph, we see what processing must be done with the cards and the box in which they are stored. Also, we discover several synonyms for the cards: data, listing, information, and house. We model them with the same objects that represent the cards. The noun people represents the outside world interacting with the program, so the rest of the paragraph describes the processing options that must be provided to the user of the program. In terms of the box of cards, the user must be able to add a new card, delete a card, print the information on the card given the owner's name, and print a list of all owners' names in the box in alphabetical order.

We can represent the cards by a class whose data members are the information written on the 3" × 5" cards. Let's call this class . How do we represent the box of cards? We have just written two versions of the abstract data type List. A list is a good candidate to simulate a box, and the information on the list can be objects of type .

So far, we have ignored the noun office. A box of cards is stored permanently in the office. A list is a structure that exists only as long as the program in which it is defined is running. But how do we keep track of the information between runs of the program? That is, how do we simulate the office in which the box resides? A file is the structure that is used for permanent storage of information. Hence, there are two representations of the box of cards. When the program is running, the box is represented as a list. When the program is not running, the box is represented as a file. The program must move the information on the cards from a file to a list as the first action, and from a list to a file as the last action.

Now we have three classes outlined: , a class that holds information about a house; a class that maintains a list of objects; and a file that contains the objects when the program is not running; and the main program.

Scenarios Before we start assigning responsibilities to these classes, we need to examine the overall processing a little more. Let's assume that the information on the 3" x 5" cards includes the owner's first and last names and the house's address, price, number of square feet, and number of bedrooms. The company never accepts more than one listing per customer, so no duplicate names appear on the cards. If an agent attempts to insert a duplicate listing, an error message is printed to the screen. As all processing is by owner's name, the combined first and last name should be the object's key.

Input We must give the user (the real estate agent) a menu of the tasks that can be performed on the list of houses. After a consultation with the agents, we decide on the following commands:

A

Add a house listing.

D

Delete a house listing.

P

Print all of the information about a house on the screen, given the name of the owner.

L

Print the names of all owners in alphabetical order on the screen.

Q

Quit.

The user continues to manipulate the list of houses until he or she enters a Q.

Notice that we have three kinds of input: the file of houses saved from the last run of the program, the commands, and data entered from the keyboard in conjunction with implementing the commands.

Output There are two kinds of output: the file of houses saved at the end of the run of the program, and screen output produced by one or more of the commands.

Data Objects In the filtering stage we came up with house objects and two container objects: the file of house objects retained from one run of the program to the next and the list that stores the house objects when the program is running. The collection of house listings is called our database.

Let's name the physical file in which we retain the house objects as houses.dat. Within our program, however, we must use two separate file stream objects, one for input and one for output, even though both refer to the same physical file. Let's call our input file masterIn (of type ifstream) and our output file masterOut (of type of stream). The object masterIn reads data from the physical file houses. dat, and the object masterOut writes to houses.dat.

Figures 3.13 and 3.14 show the general flow of the processing and the appearance of the data objects. Note that we know the internal workings of the List ADT because we wrote the code earlier in the chapter. Now, however, we write the program using only the interface as represented in the header file of the class. But which class shall we use? The unsorted version or the sorted version? Because one of the operations prints the names of the owners in order, the sorted version is a better choice.

Click To expand
Figure 3.13: The data objects in the case study
Click To expand
Figure 3.14: The high-level processing of the case study

The first time the program is run, houses.dat is empty, but it must exist; that is, we need to create an empty file and name it houses.dat. The user must build the database (the list of houses) by entering the information about the houses from the 3" x 5" cards. For example, suppose the cards contain the following information:

John Jones
304 Spider Hollow, 78744
$96,000.00
1200 sq. feet, 3 bedrooms

Susan Smith
111 Lark Lane, 78712
$105,000.00
1500 sq. feet, 3 bedrooms

Claire Clear
123 Shallow Cove
$160,000
2000 sq. feet, 4 bedrooms

The first task for the user would be to signal that a new house is to be added to the list (enter A at the prompt). The program would prompt for the information necessary, and the user would key it from the 3" x 5" cards. Each house would be put into the list of houses as it is entered. If the user then enters a Q, the information on these three houses would be written to the file houses.dat. At the next run of the program, houses.dat has three listings that are read in at the beginning of the run. The user is prompted to enter one of the commands so as to add a new listing, delete a listing, print a particular listing, print all owners, or quit.

Responsibility Algorithms Where do we go from here? Do we determine the responsibilities (member functions) for HouseType or do we determine the responsibilities for the main function next? At this stage we do not know exactly what objects of type HouseType must do, but we do know the general processing required. Let's assign responsibilities to the main function first. Although the main function is not a class, let's use a CRC card to describe its responsibilities.

Click To expand

Executing commands is clearly the central responsibility. We must first decide on the interface with which the user will manipulate the database. The operations are to add a house, delete a house, print information about one house, and print a listing of all owners. Let's define an enumeration type with these commands.

Execute Commands

Get a command
while command != Quit
   switch (command)
       case ADD      : AddHouse(houseList)
       case DELETE   : DeleteHouse(houseList)
       case PRINT_ONE: PrintHouse(houseList)
       case PRINT_ALL: PrintOwners(houseList)
   Get a command

We have called our data file houses.dat. In fact, our program would be more general if we let the user enter the name of the input file and the output file.

FileToList(SortedType& houseList, ifstream& masterln)

Prompt for and get input file name
Open file masterln
item.GetFromFile(masterln) // Reads one item from masterln.
while more data
   houseList.lnsertltem(item)
   item.GetFromFile(masterln)
Close file masterln

ListToFile(SortedType houseList, ofstream& masterOut)

Prompt for and get output file name
Open file file masterOut
houseList.ResetList()
Set length to houseList.Lengthls()
for index going from 1 to length
    houseList.GetNextItem(item)
    item.WriteToFile(masterOut)
Close file masterOut

Here are the second-level algorithms that process a command.

GetCommand(CommandType& command)

Write "Operations are listed below. "
   "Enter the appropriate uppercase letter and press return."
   "A : Add a house to the list of houses."
   "D : Delete a specific owner's house."
   "P : Print all the information about an owner's house."
   "L : Print all the names on the screen."
   "Q : Quit processing."

// Input command.
Get a letter
Set ok to false
while NOT ok
   Set ok to true
   switch (letter)
       case 'A' : Set command to ADD
       case 'D' : Set command to DELETE
       case 'P' : Set command to PRINT_ONE
       case 'L' : Set command to PRINT_ALL
       case 'Q' : Set command to QUIT
       default :  Write "Letter entered is not one of the specified "
                  "uppercase commands. Reenter and press return."
                  Get a letter
                  Set ok to false

AddHouse(SortedType& houseList)

item.GetFromUser()
houseList.Retrieveltem(item, found)
if (NOT found)
    houseList.lnsertltem(item)
else
    write "Duplicate name; operation aborted."

DeleteHouse(SortedType& houseList)

item.GetNameFromUser()
houseList.RetrieveItem (item, found)
if (found)
     houseList.Deleteltem(item)
else
     Write "Person not in list."

Note that we have to confirm that the house to be deleted is in the list because the precondition on DeleteItem in the SortedType class is that the item is present. Because the input/output is interactive, we should write a message to the user if the item is not found.

PrintHouse(SortedType houseList)

item.GetNameFromUser()
houseList.Retrieveltem(item, found)
if (found)
     item.PrintHouseToScreen()
else
     Write "Owner not in list."

PrintOwners(SortedType houseList)

houseList.ResetList()
Set length to houseList.Lengthls()
for count going from 1 TO length
    houseList.GetNextltem(item)
    item.PrintNameToScreen()

All of the second-level tasks are simple enough to code directly, so we can turn our attention to HouseType and determine its responsibilities.

Click To expand

Now we must design the data members and the responsibility algorithms. Three of the data members are strings: last name, first name, and address. We can use StrType, which was defined in Chapter 2. The other three data members (price, square feet, and bedrooms) are of simple types. We incorporate these data members into the header file, ItemType.h, along with the prototypes of the member functions required by the program.

Note that the header file must be named ItemType.h, not HouseType.h, because the code for our Sorted List ADT uses the directive #include "ItemType.h". Note also the typedef statement at the bottom of the header file. Because the Sorted List ADT expects the list items to be of type ItemType, we use typedef to make ItemType an alias for HouseType. Here is the CRC card from the SortedType class:

Click To expand

Here is the CRC card for strType with the changes made in this chapter:

Click To expand

Because all of the responsibilities for HouseType are simply member function calls to the class StrType, we leave the implementation of HouseType as an exercise. The responsibilities for SortedType and StrType have already been coded and tested. Here, then, is the client's program.

// CLIENT PROGRAM
// This program manipulates real estate property listings.
#include <iostream>
#include "SortedType.h"       // Gain access to Sorted List ADT.
#include <fstream>
#include <string>

enum CommandType {ADD, DELETE, PRINT_ONE, PRINT_ALL. QUIT};
// Each constant represents a task.
void FileToList(SortedType&. std :: ifstreams&) :
// Moves houses from file to list.
void ListToFile(SortedType, std:: ofstreams&) ;
// Moves houses from list to file.
void AddHouse(SortedType&);
// Adds a house to the list.
void DeleteHouse(SortedType&);
// Removes a house from the list.
void PrintHouse(SortedType);
// Prints a specific owner listing.
void PrintOwners(SortedType);
// Prints a sorted list of owners.
void GetCommand(CommandType&);
// Prompts for and gets next command.

int main()
{
  using namespace std;
  ifstream masterIn; // Master file of houses (input).
  ofstream masterOut; // Master file of houses (output).
  CommandType command;
  SortedType houseList;

  FileToList(houseList, masterIn);

  GetCommand(command);
  // Read and process commands until user enters a quit command.

  while (command != QUIT)
  {
    switch (command)
    {
      case ADD       : AddHouse(houseList);
                       break;
      case DELETE    : DeleteHouse(houseList);
                       break;
      case PRINT_ONE : PrintHouse(houseList);
                       break;
      case PRINT_ALL : PrintOwners(houseList);
                       break;
    }
    GetCommand(command);
  }

  ListToFile(houseList, masterOut);
  return 0;
}

//*************************************************************
// **************** Second-Level Functions ********************

void FileToList(SortedType& houseList, std::ifstream& masterIn)
// Pre:  masterIn has not been opened.
// Post: houseList contains items from masterIn.
//       masterIn has been closed.
{
  using namespace std;
  ItemType item;
  string  inFileName;

  // Prompt for file name, read file name, and prepare file.
  cout << "Enter name of file of houses; press return." << end1;
  cin >> inFileName;
  masterIn.open(inFileName.c_str());

  item.GetFromFile(masterIn); // Reads one item from masterIn.
  while (masterIn)
  {
    houseList.InsertItem(item);
    item.GetFromFile(masterIn);
  }
  masterIn.close();
}

void ListToFile(SortedType houseList, std::ofstream& masterOut)
// Pre:  masterOut has not been opened.
//       houseList has been initialized.
// Post: houseList has been written on masterOut.
//       masterOut has been closed.
{
  using namespace std;
  ItemType item;
  int length;
  string outFileName;
  cout << "Enter name of output file; press return." << end1;
  cin >> outFileName;
  masterOut.open(outFileName.c_str());

  houseList.ResetList();
  length = houseList.LengthIs();

  for (int count = 1; count <= length; count++)
  {
    houseList.GetNextItem(item);
    item.WriteToFile(masterOut);
  }

  masterOut.close();
}

void AddHouse(SortedType& houseList)
// Pre:  houseList has been initialized.
// Post: A house has been added to the list if the names are
//       not duplicated; otherwise, the operation is aborted with 
//       a message to the user.
{
  using namespace std;
  bool found; ItemType item;

  item.GetFromUser();
  houseList.RetrieveItem(item, found);
  if (!found)
  {
    houseList.InsertItem(item);
    cout << "Operation completed."  << end1;
  }
  else
    cout << "Duplicate name; operation aborted" << end1; 
}

void DeleteHouse(SortedType& houseList)
// Pre:  houseList has been initialized.
// Post: A house, specified by user input, is no longer in the list.
{
  using namespace std;
  bool found; ItemType item;

  item.GetNameFromUser();
  houseList.RetrieveItem(item, found);
  if (found)
  (
     houseList.DeleteItem(item);
     cout << "Operation completed." << end1;
  }
  else
    cout << "Person not in list." << end1; 
}

void PrintHouse(SortedType houseList)
// Pre:  houseList has been initialized.
// Post: If owner, specified by user input, is in houseList,
//       house info is printed on the screen.
{
  using namespace std;
  bool found; ItemType item;

  item.GetNameFromUser();
  houseList.RetrieveItem(item, found);
  if (found)
    item.PrintHouseToScreen();
  else
    cout << "Owner not in list." << end1;
}

void PrintOwners(SortedType houseList)
// Pre:  houseList has been initialized.
// Post: Owners' names are printed on the screen.
{
  using namespace std;
  ItemType item;
  int length;

  houseList.ResetList();
  length = houseList.LengthIs();
  for (int count = 1; count <= length; count++)
  {
    houseList.GetNextItem(item);
    item.PrintNameToScreen() ;
  }
    cout << "Operation completed." << end1;
}

void GetCommand(CommandType& command)
// Pre:  None.
// Post: User command has been prompted for and input; a valid
//       command has been found.
{
  using namespace std;
  // Prompt.
  cout << "Operations are listed below. "
       << "Enter the appropriate uppercase letter and "
       << "press return."  << end1:
  cout << "A : Add a house to the list of houses." << end1;
  cout << "D : Delete a specific owner's house."  << end1;
  cout << "P : Print the information about an owner's house."
       << end1;
  cout << "L : Print all the names on the screen."
       << end1;
  cout << "Q : Quit processing."  << end1;
  // Input command.
  char letter;
  cin >> letter;

  bool ok = false;
  while (!ok)
  {

    ok - true;
    switch (letter)
    {
      case 'A' : command = ADD;
                 break;
      case 'D' : command = DELETE;
                 break;
      case 'P' : command = PRINT_ONE;
                 break;
      case 'L' : command = PRINT_ALL:
                 break;
      case 'Q' : command = QUIT;
                 break;
      default : cout << "Letter entered is not one of the "
                     << "specified uppercase commands. "
                     << "Reenter and press return."
                     << end1;
                cin >> letter;
                ok = false; break;
    }
  }
}

In this extended example, we have walked through the design process from the informal problem statement through the coding phase. We have not, however, written a formal specification. We leave the writing of the formal specification as an exercise.

Test Plan The classes SortedType and StrType have been tested thoroughly. This leaves the class HouseType and the main function to test. To test HouseType, we would need to create a test driver program to call the member functions. Recall that these member functions were determined to be those needed by the main function (or driver). Therefore, we can use the main function as the test driver to test HouseType. In other words, we can test both together.

The first task is to create a master file of houses by using the Add command to input several houses and then quit. We also need to input a variety of commands to add more houses, delete houses, print the list of owners, and print the information about a particular owner's house. All of the error conditions must be tested thoroughly. The program must be run several times to test the access and preservation of the database (the file houses.dat). We leave the final test plan as a programming assignment.

In the discussion of object-oriented design in Chapter 1, we noted that the code responsible for coordinating the objects is called a driver. Now we can see why. In testing terminology, a driver program calls various subprograms and observes their behavior. In object-oriented terminology, a program is a collection of collaborating objects. Therefore, the role of the main function is to invoke operations on certain objects-that is, to get them started collaborating-so the term driver is appropriate. In subsequent chapters, when we use the term "driver," the meaning should be clear from the context.



Previous Section
 < Free Open Study > 
Next Section
Converted from CHM to HTML with chm2web Pro 2.85 (unicode)