Previous | Next | Table of Contents | Index | Program List | Copyright

10.5 System Structures: Writing an ADT for Employee Records

In this section we will develop an ADT for employee records that could be used in a larger database application. This ADT also uses the Dates and Currency ADTs from Sections 10.3 and 10.4, respectively, and will be used in Section 10.6 to produce an interactive query system for employees. For our purposes, an employee record will contain six fields:

Program 10.11 shows the specification of a package Employees. Note that the constant MaxName, the subtypes IDType and NameType, and the types GenderType and Employee are provided in the specification. For reasons discussed several times in this chapter, the record type Employee is PRIVATE so that client programs do not have direct access to the field names or structure of the record. (A justification for this might be the intention to add more fields in the future that are never accessed by clients but are handled purely internally by the package body.)

Program 10.11
Specification for Employees Package

WITH Currency;
WITH Dates;
PACKAGE Employees IS
------------------------------------------------------------------------
--| Specification for ADT package to handle Employee records
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
------------------------------------------------------------------------

  -- constant and type definitions

  MaxName: CONSTANT Positive := 30;
  SUBTYPE NameType IS String(1..MaxName);   

  SUBTYPE IDType IS Positive RANGE 1111..9999;
  TYPE    GenderType IS (Female, Male);

  TYPE Employee IS PRIVATE;

  -- operations

  -- constructor

  FUNCTION MakeEmployee (ID:        IDType; 
                         Name:      NameType; 
                         Gender:    GenderType; 
                         NumDepend: Natural;
                         Salary:    Currency.Quantity;
                         StartDate: Dates.Date)  RETURN Employee;
  -- Pre: all input parameters are defined
  -- Post: returns a value of type Employee

  -- selectors

  FUNCTION RetrieveID        (OneEmp: Employee) RETURN IDType;
  FUNCTION RetrieveName      (OneEmp: Employee) RETURN NameType;
  FUNCTION RetrieveGender    (OneEmp: Employee) RETURN GenderType;
  FUNCTION RetrieveNumDepend (OneEmp: Employee) RETURN Natural;   
  FUNCTION RetrieveSalary    (OneEmp: Employee) 
                                         RETURN Currency.Quantity;
  FUNCTION RetrieveDate      (OneEmp: Employee) RETURN Dates.Date;
  -- Pre: OneEmp is defined
  -- Post: each selector retrieves its desired field

PRIVATE

  TYPE Employee IS RECORD
    ID:        IDType := IDType'Last;
    Name:      NameType := (OTHERS => ' ');
    Gender:    GenderType := Female;
    NumDepend: Natural := 0;
    Salary:    Currency.Quantity := Currency.MakeCurrency(0.00);
    StartDate: Dates.Date := Dates.Date_Of(1980, Dates.Jan, 1);
  END RECORD;

END Employees;
Because a client program cannot get into the details of an employee record, the ADT package must provide a set of constructor and selector operations. These are shown in the specification as the constructor MakeEmployee and the selectors RetrieveName, RetrieveGender, RetrieveNumDepend, RetrieveSalary, and RetrieveDate. The body of this relatively simple package is given in Program 10.12. Additional operations on employee records depend upon how the records will be used, as you will see in the next section.

Program 10.12
Body of Employees Package

PACKAGE BODY Employees IS
------------------------------------------------------------------------
--| Body of ADT package to handle Employee records
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
------------------------------------------------------------------------

  -- operations

  -- constructor

  FUNCTION MakeEmployee (ID:        IDType;
                         Name:      NameType;
                         Gender:    GenderType;
                         NumDepend: Natural;
                         Salary:    Currency.Quantity;
                         StartDate: Dates.Date)  RETURN Employee IS

    TempRecord: Employee;

  BEGIN -- MakeEmployee

    TempRecord := 
      (ID => ID, Name => Name, 
       Gender => Gender, NumDepend => NumDepend, 
       Salary => Salary, StartDate => StartDate);
    RETURN TempRecord;

  END MakeEmployee;

  FUNCTION RetrieveID (OneEmp: Employee) RETURN IDType IS
  BEGIN
    RETURN OneEmp.ID;
  END RetrieveID;

  FUNCTION RetrieveName (OneEmp: Employee) RETURN NameType IS
  BEGIN
    RETURN OneEmp.Name;
  END RetrieveName;

  FUNCTION RetrieveGender (OneEmp: Employee) RETURN GenderType IS
  BEGIN
    RETURN OneEmp.Gender;
  END RetrieveGender;

  FUNCTION RetrieveNumDepend (OneEmp: Employee) RETURN Natural IS   
  BEGIN
    RETURN OneEmp.NumDepend;
  END RetrieveNumDepend;

  FUNCTION RetrieveSalary (OneEmp: Employee) RETURN Currency.Quantity IS
  BEGIN
    RETURN OneEmp.Salary;
  END RetrieveSalary;

  FUNCTION RetrieveDate (OneEmp: Employee) RETURN Dates.Date IS
  BEGIN
    RETURN OneEmp.StartDate;
  END RetrieveDate;

END Employees;
In Program 10.13 and Program 10.14, we give the specification and body for a child package for simple employee input and output, providing procedures ReadEmployee and DisplayEmployee. The read procedure is not robust; invalid input will result in program termination. Similarly, display procedure merely copies the fields onto the screen, with no additional formatting. As an exercise, you can improve this child package and write a program to test it and the parent package Employees.

Program 10.13
Specification for Employees.IO Child Package

PACKAGE Employees.IO IS
------------------------------------------------------------------------
--| Child Package for Employee Input/Output
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
------------------------------------------------------------------------

  PROCEDURE ReadEmployee (Item: OUT Employee);
  -- reads an Employee record from the terminal
  -- Pre: none
  -- Post: Item contains a record of type Employee

  PROCEDURE DisplayEmployee (Item: IN Employee);
  -- displays an Employee record on the screen
  -- Pre: Item is defined
  -- Post: displays the fields of Item on the screen

END Employees.IO;

Program 10.14
Body of Employees.IO Child Package

WITH Ada.Text_IO;
WITH Ada.Float_Text_IO;
WITH Ada.Integer_Text_IO;
WITH Dates.IO;
WITH Currency.IO;
PACKAGE BODY Employees.IO IS
------------------------------------------------------------------------
--| Body of Child Package for Employee Input/Output
--| Author: Michael B. Feldman, The George Washington University
--| Last Modified: July 1995        
------------------------------------------------------------------------

  PACKAGE GenderType_IO IS 
    NEW Ada.Text_IO.Enumeration_IO(Enum => GenderType);

  PROCEDURE ReadEmployee (Item: OUT Employee) IS

    S: String(1..MaxName);
    Count: Natural;

  BEGIN -- simple, non-robust ReadEmployee   
  
    Ada.Text_IO.Put(Item => "ID > ");  
    Ada.Integer_Text_IO.Get(Item => Item.ID);  
    Ada.Text_IO.Skip_Line;

    Ada.Text_IO.Put(Item => "Name > ");
    Ada.Text_IO.Get_Line(Item => S, Last => Count);
    Item.Name(1..Count) := S(1..Count);

    Ada.Text_IO.Put(Item => "Gender (Female or Male) > ");  
    GenderType_IO.Get(Item => Item.Gender);

    Ada.Text_IO.Put(Item => "Number of dependents > ");
    Ada.Integer_Text_IO.Get(Item => Item.NumDepend);

    Ada.Text_IO.Put(Item => "Salary > ");
    Currency.IO.Get(Item => Item.Salary);

    Ada.Text_IO.Put(Item => "Starting Date, mmm dd yyyy > ");
    Dates.IO.Get(Item => Item.StartDate);
  
  END ReadEmployee;

  PROCEDURE DisplayEmployee (Item: IN Employee) IS

  BEGIN -- simple DisplayEmployee

    Ada.Integer_Text_IO.Put(Item => Item.ID, Width => 1);  
    Ada.Text_IO.New_Line;
    Ada.Text_IO.Put(Item => Item.Name);
    Ada.Text_IO.New_Line;
    GenderType_IO.Put(Item => Item.Gender);
    Ada.Text_IO.New_Line;
    Ada.Integer_Text_IO.Put(Item => Item.NumDepend, Width => 1);
    Ada.Text_IO.New_Line;
    Currency.IO.Put(Item => Item.Salary);
    Ada.Text_IO.New_Line;
    Dates.IO.Put(Item => Item.StartDate, Format => Dates.IO.Full);
    Ada.Text_IO.New_Line;

  END DisplayEmployee;

END Employees.IO;
It is worth mentioning that the input/output procedures are making direct references to the employee fields (e.g., Item.Gender), even though Employee is a PRIVATE type. This shows an essential difference between a child package, which can be thought of as a separate part of the original parent, and a client package or program, which just uses the package. A child package, being part of a "family," has knowledge of private family details that are not available to clients. Naturally, as is the case with human families, this knowledge of private details must be used with care!


Case Study: Employee Inquiry System

To show a useful application of the package Employees, we introduce a case study involving an interactive query system that allows the user to build and modify a data base of employee records.

PROBLEM SPECIFICATION

We have a small company with no more than 25 employees. We wish to allow an interactive user to enter employee information into a computer and be able to do the following kinds of operations:

ANALYSIS

Because we already have a package that can handle individual employee records, we have two tasks ahead of us:

  1. Develop a way of holding a number of records and performing the above operations
  2. Develop a way for an interactive user to enter commands into the system

The two tasks are best separated into a set of operations that manipulate the data base without concern for any user interaction, and a "user interface" that can handle user interactions without concern for the details of the data base operations. This is a very common approach to separation of concerns in designing a system.

DESIGN

In keeping with the separation outlined above, we design the following system components:

  1. A data base package, a set of operations in the form of procedures that a client program can call. The user of this part of the system, like the user of the employee package, is a programmer who is creating a larger application. The same database package could be used by many different applications, one of which is number 2 below.
  2. A user interface program, in our case a menu-driven program to allow a user to select from a set of commands to do the functions listed above. Here, the user is an end user, a member of the company such as the personnel or payroll manager, not a programmer.
Program 10.15 shows the specification for the data base package. The programmer using this package sees only a set of operations; the data base itself is encapsulated in the body of the package, as we shall see.

Program 10.15
Specification for Database Package

WITH Employees;
PACKAGE Database IS
------------------------------------------------------------------------
--| Specification of the abstract data object for a database
--| of employee records
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
------------------------------------------------------------------------

  -- Exported Exception

  DatabaseFull: EXCEPTION;

  -- Operations  

  PROCEDURE Initialize;
  -- Pre : None
  -- Post: Database is emptied of all records
     
  PROCEDURE Insert (E       : Employees.Employee;
                    Success : OUT Boolean);
  --  Pre   : E is defined
  --  Post  : Inserts new element E into database
  --    Success is True if insertion is performed, and False
  --    if database already has an element with the same ID as E.
  --  Raises: DatabaseFull if the database is full before insertion
     
  PROCEDURE Replace (E       : Employees.Employee;
                     Success : OUT Boolean);
  --  Pre   : E is defined
  --  Post  : Finds record in database with E's ID, and replaces it
  --    with E. Success is True if replacement is performed, and False
  --    if database has no element with the same ID as E.
     
  PROCEDURE Retrieve (ID      : IN  Employees.IDType;
                      E       : OUT Employees.Employee;
                      Success : OUT Boolean);
  --  Pre : ID is defined
  --  Post: Copies into E the database record with the given ID 
  --    Success is True if the copy is performed, and False
  --    if database has no element with the given ID   
     
  PROCEDURE Delete (ID      : IN Employees.IDType;
                    Success : OUT Boolean);
  --  Pre : ID is defined
  --  Post: Deletes from database the record with the given ID 
  --    Success is True if deletion is performed, and False
  --    if database has no element with the given ID   

  PROCEDURE Display;
  --  Pre : None
  --  Post: The database records are displayed in order by ID

END Database;

TEST PLAN

The data base operations can be tested by a simple program consisting of a number of calls to procedures in the package. A number of operations need to be done, just to be certain that they all operate correctly. Specifically, note that the operations all have a "successful" result and a "not successful" result. Test cases must be carefully chosen to be sure that all operations behave correctly whether the result is successful or not.

IMPLEMENTATION

The body of the data base package is given in Program 10.16. As can be seen from the types and other declarations, the data base is a simple structure: We are just using an array to store the employee records; this array is contained in a record along with a field indicating the number of records stored in the array. The entire company's records are stored in the data base variable Company.

Program 10.16
Body of Database Package

WITH Ada.Text_IO;
WITH Employees;
WITH Employees.IO;
PACKAGE BODY Database IS
------------------------------------------------------------------------
--| Body of the abstract data object for a database
--| of employee records
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
------------------------------------------------------------------------

  -- declarations for the Employee database, TableType

  MaxData: CONSTANT Positive := 25;

  SUBTYPE CompanyIndex IS Natural RANGE 1..MaxData;
  SUBTYPE CompanyRange IS Natural RANGE 0..MaxData;
  TYPE DataArray IS ARRAY(CompanyIndex) OF Employees.Employee;

  TYPE TableType IS RECORD
    Data: DataArray;
    CurrentSize:    CompanyRange := 0;
  END RECORD;
  
  Company: TableType;

  PROCEDURE Initialize IS
  BEGIN -- Initialize
    Company.CurrentSize := 0;
  END Initialize;

  PROCEDURE Insert (E       : Employees.Employee;
                    Success : OUT Boolean) IS
  BEGIN -- Insert

    Success := True;

    -- First search database for E's ID; set Success false if found
    FOR Which IN 1..Company.CurrentSize LOOP
      IF Employees.RetrieveID(Company.Data(Which)) = 
         Employees.RetrieveID(E) THEN
        Success := False;
        RETURN;
      END IF;
    END LOOP;

    -- we didn't find a matching record, so we can insert this one
    Company.CurrentSize := Company.CurrentSize + 1;
    Company.Data(Company.CurrentSize) := E;

  END Insert;
     
  PROCEDURE Replace (E       : Employees.Employee;
                     Success : OUT Boolean) IS
  BEGIN -- stub
    Ada.Text_IO.Put(Item => "Replace is still under construction.");
    Ada.Text_IO.New_Line;
  END Replace;
     
  PROCEDURE Retrieve (ID      : IN  Employees.IDType;
                      E       : OUT Employees.Employee;
                      Success : OUT Boolean) IS
  BEGIN -- stub
    Ada.Text_IO.Put(Item => "Retrieve is still under construction.");
    Ada.Text_IO.New_Line;
  END Retrieve;
     
  PROCEDURE Delete (ID      : IN Employees.IDType;
                    Success : OUT Boolean) IS
  BEGIN -- stub
    Ada.Text_IO.Put(Item => "Delete is still under construction.");
    Ada.Text_IO.New_Line;
  END Delete;

  PROCEDURE Display IS
  BEGIN -- Display

    FOR Which IN 1..Company.CurrentSize LOOP
      Employees.IO.DisplayEmployee (Item => Company.Data(Which));
      Ada.Text_IO.New_Line;
    END LOOP;
    
  END Display;

END Database;
Three operations are fully coded in this package:

The rest of the operations are left as an exercise. They are coded as stubs; if one is called, it simply displays an "under construction" message.

Given the single constructor and field-by-field selector operations provided by package Employees, the best way to change a single field in the record is to retrieve the record with a Retrieve call, then retrieve the individual fields, change the desired ones, and construct a new record, calling Replace to put it back in the data base. An alternative design would modify the employee package with some new constructor operations, each of which modifies a single field of its record parameter.

Finally, because the records are kept in the array in no particular order, the easiest way to delete a record with a given array subscript is just to copy the last record in the array into that position, then to decrement the variable in which you keep track of how many records are present. That is, if there are 20 records in the 100-element array, and you wish to delete record number 7, just copy record number 20 into position 7, and change the number of records to 19.

Program 10.17 gives a simple test program, which you can use as an example to build a more elaborate one. For brevity, we omit the sample run.

Program 10.17
Simple Test of Database Package

WITH Ada.Text_IO;
WITH Employees;
WITH Employees.IO;
WITH Database;
PROCEDURE Test_Employee IS
------------------------------------------------------------------------
--| Simple Test of Employee Database
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
--|                                                              
------------------------------------------------------------------------

  E: Employees.Employee;
  Success: Boolean;

BEGIN -- Test_Employee

  Database.Initialize;
  
  FOR Count IN 1..3 LOOP

    Employees.IO.ReadEmployee(Item => E);
    Database.Insert(E => E, Success => Success);
    Ada.Text_IO.Put(Item => "--------------------");
    Ada.Text_IO.New_Line;
    Database.Display;
    Ada.Text_IO.Put(Item => "--------------------");
    Ada.Text_IO.New_Line;

  END LOOP;

END Test_Employee;
Finally, Program 10.18 shows a partially complete menu-driven user interface. This consists of two main functions in a main loop:
  1. Display the menu and get a command robustly
  2. Carry out the desired command, using a CASE statement to determine which command was entered

The first function is coded; the second is given as a skeleton for you to fill in.

Program 10.18
User Interface for Database Package

WITH Ada.Text_IO;
WITH Screen;
WITH Database;
PROCEDURE Employee_UI IS
------------------------------------------------------------------------
--| Skeleton of menu-driven user interface for Employee Database.
--| When correct input is entered, a message is displayed
--| instead of actually executing the command .
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
------------------------------------------------------------------------

  TYPE MenuValues IS (I,      -- Initialize database
                      A,      -- Add a record
                      D,      -- Delete a record
                      F,      -- retrieve (Find) and display a record
                      R,      -- find and Replace a record
                      P,      -- Display all records
                      Q);     -- Quit the program

  PACKAGE Menu_IO IS
    NEW Ada.Text_IO.Enumeration_IO (Enum => MenuValues);

  MenuSelection : MenuValues;

BEGIN -- Employee_UI

  LOOP -- main program loop

    Screen.ClearScreen;
    Screen.MoveCursor (Row => 5, Column => 20);
    Ada.Text_IO.Put (Item => "Select one of the operations below.");
    Screen.MoveCursor (Row => 7, Column => 20);
    Ada.Text_IO.Put (Item => "I  Initialize the Employee Database");
    Screen.MoveCursor (Row => 8, Column => 20);
    Ada.Text_IO.Put (Item => "A  Add a New Employee to the Database");
    Screen.MoveCursor (Row => 9, Column => 20);
    Ada.Text_IO.Put (Item => "D  Delete an Employee from the Database");
    Screen.MoveCursor (Row => 10, Column => 20);
    Ada.Text_IO.Put (Item => "F  Find and Display One Employee");
    Screen.MoveCursor (Row => 11, Column => 20);
    Ada.Text_IO.Put (Item => "R  Replace Old Record with New One");
    Screen.MoveCursor (Row => 11, Column => 20);
    Ada.Text_IO.Put (Item => "P  Display All Records in the Database");
    Screen.MoveCursor (Row => 12, Column => 20);
    Ada.Text_IO.Put (Item => "Q  Exit the program");

    LOOP
      BEGIN -- exception handler block

        Screen.MoveCursor (Row => 14, Column => 20);
        Ada.Text_IO.Put ("Please type a command, then press Enter > ");

        -- this statement will raise Data_Error if input is invalid
        Menu_IO.Get (Item => MenuSelection);

        -- these statements will be executed only if 
        -- the input is correct; otherwise,
        -- control passes to exception handler
        Screen.MoveCursor (Row => 15, Column => 20);
        Ada.Text_IO.Put ("Thank you for correct input.");
        Ada.Text_IO.New_Line;
        EXIT;      -- valid data; go ahead to process it

      EXCEPTION    -- invalid data
        
        WHEN Ada.Text_IO.Data_Error =>
          Screen.Beep;
          Screen.MoveCursor (Row => 15, Column => 20);
          Ada.Text_IO.Put (Item => "Value entered is not a command.");
          Ada.Text_IO.New_Line;
          DELAY 1.0;
          Ada.Text_IO.Skip_Line;
          Screen.MoveCursor (Row => 15, Column => 20);
          Ada.Text_IO.Put (Item => "                               ");
        WHEN OTHERS =>
          Screen.Beep;
          Screen.MoveCursor (Row => 15, Column => 20);
          Ada.Text_IO.Put (Item => "Unknown error; try again, please.");
          Ada.Text_IO.New_Line;
          DELAY 1.0;
          Ada.Text_IO.Skip_Line;
          Screen.MoveCursor (Row => 15, Column => 20);
          Ada.Text_IO.Put (Item => "                                 ");
        
      END;         -- of exception handler block

    END LOOP;

    Screen.MoveCursor (Row =>22, Column => 20);
    CASE MenuSelection IS
      WHEN I =>
        Ada.Text_IO.Put (Item => "I entered; here we'd initialize");
      WHEN A =>
        Ada.Text_IO.Put (Item => "A entered; here we'd insert");
      WHEN D =>
        Ada.Text_IO.Put (Item => "D entered; here we'd delete");
      WHEN F =>
        Ada.Text_IO.Put (Item => "F entered; here we'd find");
      WHEN R =>
        Ada.Text_IO.Put (Item => "R entered; here we'd replace");
      WHEN P =>
        Ada.Text_IO.Put (Item => "P entered; here we'd display all");
      WHEN Q =>
        Ada.Text_IO.Put (Item => "Q entered; have a niiiiccce day.");
        EXIT;   -- the main loop and quit the program
    END CASE;

    Ada.Text_IO.New_Line;
    DELAY 2.0;

  END LOOP;

END Employee_UI;

Abstract Data Types and Abstract Data Objects

Our database implementation is actually what is known as an abstract data object (ADO) implementation. An ADO differs from an ADT in that an ADT, as we have seen in the rational, date, currency, and employee cases, provides a type so that client programs can declare variables ("objects") of the type, whereas an ADO encapsulates a single object in the package body, unseen by client programs and manipulated only by calls to the ADO operations.

An alternative data base design would turn the package into an ADT, which would provide a PRIVATE type TableType to client programs. The client then could declare several data bases, for example, one for each of the company's several offices. Each data base operation would have a parameter indicating which data base was being manipulated.

Exercises for Section 10.5

Programming

  1. In the package Employees.IO, revise the body of the procedure ReadEmployee to make it robust, and the procedure DisplayEmployee to provide "prettier" formatting of the output. Also, write a program to test the package Employees.
  2. Add to package Employees.IO two procedures GetEmployee and PutEmployee that read and write employee records using a disk file.
  3. Complete the case study in this section by completing the data base operations and by doing a detailed design, structure chart, algorithm specification, and coding for the menu-driven inquiry program.


Previous | Next | Table of Contents | Index | Program List | Copyright

Copyright © 1996 by Addison-Wesley Publishing Company, Inc.