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
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
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
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!
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:
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:
Program 10.15
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
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:
Initialize
clears out the array by setting its
CurrentSize
field to zero. There is no need actually to "erase"
the records themselves; they will just be overwritten by newly arriving records.
Insert
loops through the array looking for a record with the
given ID. As given in the postconditions for this operation, if a record is
found, the insertion fails, because otherwise we would be inserting two
or more employee records with the same ID. If we search the entire occupied
part of the array without finding a record with the same ID, we just store the
new record in the next available array cell.
Display
loops through the array, calling the employee display
procedure repeatedly.
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
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:
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
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;
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.
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
.
Employees.IO
two procedures
GetEmployee
and PutEmployee
that read and write
employee records using a disk file.
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.