In this section we introduce a package to represent, read, and display various geometric figures, including their areas and perimeters. We need to provide first a representation scheme for geometric figures, with a useful set of operations and second, a means for interactive users to read and display these figures. As in other ADTs we have developed, it is useful to separate these two concerns.
We first develop an abstract data type that allows a client program to
construct a geometric figure. The characteristics for a circle are different
from those for a rectangle (a square is a rectangle whose width and height are
equal), so we use a record with a variant part. In this case, the fixed part of
the record will contain its area and perimeter, which are computed
automatically as the figure is constructed. Here is the variant type
Figure
:
SUBTYPE NonNegFloat IS Float RANGE 0.0 .. Float'Last; TYPE FigKind IS ( Rectangle, Square, Circle); TYPE Figure ( FigShape : FigKind := Rectangle) IS RECORD Area : NonNegFloat := 0.0; Perimeter : NonNegFloat := 0.0; CASE FigShape IS WHEN Rect | Square => Width : NonNegFloat := 0.0; Height : NonNegFloat := 0.0; WHEN Circle => Radius : NonNegFloat := 0.0; END CASE; END RECORD;
The package specification appears as Program 12.4.
Program 12.4
PACKAGE Geometry IS ------------------------------------------------------------------------ --| Defines an abstract data type for a geometric figure. --| Operations include constructors for rectangles, circles, --| and squares, and selectors for width, height, side, --| area and perimeter. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------ -- Data Types SUBTYPE NonNegFloat IS Float RANGE 0.0 .. Float'Last; TYPE FigKind IS (Rectangle, Square, Circle); TYPE Figure (FigShape : FigKind := Rectangle) IS PRIVATE; -- Exported Exception ShapeError: EXCEPTION; -- Constructor Operations FUNCTION MakeRectangle (Width, Height : NonNegFloat) RETURN Figure; -- Pre : Width and Height are defined -- Post: returns a rectangle FUNCTION MakeCircle (Radius : NonNegFloat) RETURN Figure; -- Pre : Radius is defined -- Post: returns a circle FUNCTION MakeSquare (Side : NonNegFloat) RETURN Figure; -- Pre : Side is defined -- Post: returns a square -- selectors FUNCTION Shape (OneFig : Figure) RETURN FigKind; FUNCTION Height (OneFig : Figure) RETURN NonNegFloat; FUNCTION Width (OneFig : Figure) RETURN NonNegFloat; FUNCTION Radius (OneFig : Figure) RETURN NonNegFloat; FUNCTION Side (OneFig : Figure) RETURN NonNegFloat; FUNCTION Perimeter (OneFig : Figure) RETURN NonNegFloat; FUNCTION Area (OneFig : Figure) RETURN NonNegFloat; -- Pre : OneFig is defined. -- Post : Returns the appropriate characteristic -- Raises: ShapeError if the requested characteristic is -- undefined for the shape of OneFig PRIVATE TYPE Figure (FigShape : FigKind := Rectangle) IS RECORD Area : NonNegFloat := 0.0; Perimeter : NonNegFloat := 0.0; CASE FigShape IS WHEN Rectangle | Square => Width : NonNegFloat := 0.0; Height : NonNegFloat := 0.0; WHEN Circle => Radius : NonNegFloat := 0.0; END CASE; END RECORD; END Geometry;
We
have defined the data type Figure
as a PRIVATE
type.
Why? If the client program had access to the details of the record representing
the figure, it could, for example, change the Perimeter
field by
simply plugging in a new number. Because the figure would no longer make
geometric sense, this action would violate the abstraction. Note the syntax for
declaring a PRIVATE
type with a variant: The discriminant appears
first in the partial declaration and later in the complete declaration in the
PRIVATE
part of the specification.
The following design decisions make the data type safe from accidental misuse:
PRIVATE
to keep client programs
from prying into, and changing, fields of the record, such as the area and the
perimeter, or changing the length of the side without changing the area and
perimeter fields accordingly.
The operations in the package are three constructors,
MakeRecangle
, MakeCircle
, and
MakeSquare
, which construct the appropriate variant given the
relevant characteristics, and a set of selectors Shape
,
Width
, Height
, Side
,
Radius
, Area
, and Perimeter
, which
return these characteristics of the figure. Note that even though a square and
a rectangle use the same variant, the constructors and selectors are different
for them. Also, we export an exception ShapeError
to prevent a
client from applying an inappropriate selector (e.g., finding the radius of a
square).
A client program can declare variables of type Figure
in either
constrained or unconstrained form. The variable
SomeShape : Figure;can hold, at different moments, a circle, a square, or a rectangle; it is unconstrained. However,
BigSquare : Figure (FigShape => Square);can hold only a square, because it is constrained; that is, we plugged a discriminant value into the declaration of the variable and are now "locked in" to that value.
Program
12.5 shows the package body for Geometry
.
Program 12.5
Geometry
Package
WITH Ada.Numerics; USE Ada.Numerics; PACKAGE BODY Geometry IS ------------------------------------------------------------------------ --| Body of abstract data type package for geometric figures. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------ -- Body of abstract data type package for geometric figures. -- internal functions, not exported to client. ComputePerimeter -- and ComputeArea are used to ensure that all figures are -- constructed with these attributes automatically inserted. -- The exported selectors Perimeter and Area assume that these -- fields have been set by the internal functions. FUNCTION ComputePerimeter (OneFig : Figure) RETURN NonNegFloat IS -- Pre : The discriminant and characteristics of OneFig are defined. -- Post: Returns Perimeter of OneFig. BEGIN -- ComputePerimeter CASE OneFig.FigShape IS WHEN Rectangle => RETURN 2.0 * (OneFig.Width + OneFig.Height); WHEN Square => RETURN 4.0 * OneFig.Width; WHEN Circle => RETURN 2.0 * Pi * OneFig.Radius; END CASE; END ComputePerimeter; FUNCTION ComputeArea (OneFig : Figure) RETURN NonNegFloat IS -- Pre : The discriminant and characteristics of OneFig are defined. -- Post: Returns Area of OneFig. BEGIN -- ComputeArea CASE OneFig.FigShape IS WHEN Rectangle => RETURN OneFig.Width * OneFig.Height; WHEN Square => RETURN OneFig.Width ** 2; WHEN Circle => RETURN Pi * OneFig.Radius ** 2 ; END CASE; END ComputeArea; -- Exported Operations FUNCTION MakeRectangle (Width, Height : NonNegFloat) RETURN Figure IS Result : Figure(FigShape => Rectangle); BEGIN -- MakeRectangle Result.Height := Height; Result.Width := Width; Result.Area := ComputeArea(Result); Result.Perimeter := ComputePerimeter(Result); RETURN Result; END MakeRectangle; FUNCTION MakeCircle (Radius : NonNegFloat) RETURN Figure IS Result: Figure (FigShape => Circle); BEGIN -- MakeCircle Result.Radius := Radius; Result.Area := ComputeArea(Result); Result.Perimeter := ComputePerimeter(Result); RETURN Result; END MakeCircle; FUNCTION MakeSquare (Side : NonNegFloat) RETURN Figure IS Result: Figure (FigShape => Square); BEGIN -- MakeSquare Result.Height := Side; Result.Width := Side; Result.Area := ComputeArea(Result); Result.Perimeter := ComputePerimeter(Result); RETURN Result; END MakeSquare; FUNCTION Shape (OneFig : Figure) RETURN FigKind IS BEGIN -- Perimeter RETURN OneFig.FigShape; END Shape; FUNCTION Perimeter (OneFig : Figure) RETURN NonNegFloat IS BEGIN -- Perimeter RETURN OneFig.Perimeter; END Perimeter; FUNCTION Area (OneFig : Figure) RETURN NonNegFloat IS BEGIN -- Area RETURN OneFig.Area; END Area; FUNCTION Height (OneFig : Figure) RETURN NonNegFloat IS BEGIN -- Height CASE OneFig.FigShape IS WHEN Rectangle | Square => RETURN OneFig.Height; WHEN OTHERS => RAISE ShapeError; END CASE; END Height; FUNCTION Width (OneFig : Figure) RETURN NonNegFloat IS BEGIN -- Width CASE OneFig.FigShape IS WHEN Rectangle | Square => RETURN OneFig.Width; WHEN OTHERS => RAISE ShapeError; END CASE; END Width; FUNCTION Side (OneFig : Figure) RETURN NonNegFloat IS BEGIN -- Side CASE OneFig.FigShape IS WHEN Square => RETURN OneFig.Height; WHEN OTHERS => RAISE ShapeError; END CASE; END Side; FUNCTION Radius (OneFig : Figure) RETURN NonNegFloat IS BEGIN -- Radius CASE OneFig.FigShape IS WHEN Circle => RETURN OneFig.Radius; WHEN OTHERS => RAISE ShapeError; END CASE; END Radius; END Geometry;
The
constructor functions create the appropriate variant of the record from the
relevant components, then calculate the area and perimeter. Local functions
ComputeArea
and ComputePerimeter
are used to assist.
These are not given in the specification. The user can find out the area and
perimeter by calling the appropriate selector, whose code is straightforward.
Note that even though a square is also a rectangle, we distinguish between them
in many of the operations. Note in many of these operations how a
CASE
statement is used to control the processing of the variant
data.
As in earlier ADTs, we separate the input/output
operations into a child package.
Program
12.6 and
Program
12.7 give the specification and body for Geometry.IO
.
Procedure Get
reads in the enumeration value denoting the kind of
figure, reads the data required for the kind of figure indicated by the
discriminant field, and calls the appropriate constructor. This procedure
serves as a good example of how to read a variant record from the interactive
user. As before, in the Get
and Put
procedures, a
CASE
statement controls the processing of the data in the variant
part.
Program 12.6
Geometry.IO
Child Package
PACKAGE Geometry.IO IS ------------------------------------------------------------------------ --| Child Package: Input/Output for Geometric Figures --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------ PROCEDURE Get (Item : OUT Geometry.Figure); -- Pre : None -- Post: Item contains a geometric figure. PROCEDURE Put (Item : IN Geometry.Figure); -- Pre : Item is defined. -- Post: Item is displayed. END Geometry.IO;
Program 12.7
Geometry.IO
Child Package
WITH Ada.Float_Text_IO; WITH Ada.Text_IO; WITH Robust_Input; PACKAGE BODY Geometry.IO IS ------------------------------------------------------------------------ --| Body of Input/Output Child Package for Geometric Figures --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------ MaxSize: CONSTANT NonNegFloat := 1_000_000.0; PACKAGE FigKind_IO IS NEW Ada.Text_IO.Enumeration_IO (Enum => FigKind); -- Local procedure ReadShape and RobustGet are used only within -- the package, therefore not exported. PROCEDURE ReadShape (Item : OUT FigKind) IS -- Pre: none -- Post: Item contains a figure kind. ReadShape reads robustly. TempItem: FigKind; BEGIN -- ReadShape LOOP BEGIN Ada.Text_IO.Put (Item => "Enter a shape: rectangle, circle, square > "); FigKind_IO.Get(Item => TempItem); Item := TempItem; EXIT; EXCEPTION WHEN Ada.Text_IO.Data_Error => Ada.Text_IO.Put ("Value not a valid shape. Please try again."); Ada.Text_IO.New_Line; Ada.Text_IO.Skip_Line; END; END LOOP; -- assert: Item is rect, circle, or square END ReadShape; PROCEDURE Get (Item : OUT Figure) IS Shape : FigKind; Height : NonNegFloat; Width : NonNegFloat; Side : NonNegFloat; Radius : NonNegFloat; BEGIN -- Get -- Read the shape character and define the discriminant ReadShape(Shape); -- Select the proper variant and read pertinent data CASE Shape IS WHEN Rectangle => Ada.Text_IO.Put(Item => "Enter width."); Ada.Text_IO.New_Line; Robust_Input.Get (Item => Width, MinVal => 0.0, MaxVal => MaxSize); Ada.Text_IO.Put(Item => "Enter height."); Ada.Text_IO.New_Line; Robust_Input.Get (Item => Height, MinVal => 0.0, MaxVal => MaxSize); Item := MakeRectangle(Width, Height); WHEN Square => Ada.Text_IO.Put(Item => "Enter length of side."); Ada.Text_IO.New_Line; Robust_Input.Get (Item => Side, MinVal => 0.0, MaxVal => MaxSize); Item := MakeSquare(Side); WHEN Circle => Ada.Text_IO.Put(Item => "Enter circle radius."); Ada.Text_IO.New_Line; Robust_Input.Get (Item => Radius, MinVal => 0.0, MaxVal => MaxSize); Item := MakeCircle(Radius); END CASE; END Get; PROCEDURE Put (Item: IN Figure) IS BEGIN -- DisplayFigure -- Display shape and characteristics Ada.Text_IO.Put(Item => "Figure shape: "); FigKind_IO.Put(Item => Shape(Item), Width => 1); Ada.Text_IO.New_Line; CASE Item.FigShape IS WHEN Rectangle => Ada.Text_IO.Put(Item => "height = "); Ada.Float_Text_IO.Put (Item => Height(Item), Fore=>1, Aft=>2, Exp=>0); Ada.Text_IO.Put(Item => "; width = "); Ada.Float_Text_IO.Put (Item => Width(Item), Fore=>1, Aft=>2, Exp=>0); WHEN Square => Ada.Text_IO.Put(Item => "side = "); Ada.Float_Text_IO.Put (Item => Height(Item), Fore=>1, Aft=>2, Exp=>0); WHEN Circle => Ada.Text_IO.Put(Item => "radius = "); Ada.Float_Text_IO.Put (Item => Radius(Item), Fore=>1, Aft=>2, Exp=>0); END CASE; Ada.Text_IO.Put(Item => "; perimeter = "); Ada.Float_Text_IO.Put (Item => Perimeter(Item), Fore=>1, Aft=>2, Exp=>0); Ada.Text_IO.Put(Item => "; area = "); Ada.Float_Text_IO.Put(Item => Area(Item), Fore=>1, Aft=>2, Exp=>0); Ada.Text_IO.New_Line; END Put; END Geometry.IO;
Program 12.8 shows a brief and straightforward test program for the package.
Program 12.8
WITH Ada.Text_IO; WITH Ada.Integer_Text_IO; WITH Geometry; WITH Geometry.IO; PROCEDURE Test_Geometry IS ------------------------------------------------------------------------ --| Program to test package Geometry --| Author: Michael B. Feldman, The George Washington University --| Last Modified: September 1995 ------------------------------------------------------------------------ MyFig : Geometry.Figure; -- a figure BEGIN -- Test_Geometry FOR TestTrial IN 1..3 LOOP Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => " Trial #"); Ada.Integer_Text_IO.Put(Item => TestTrial, Width => 1); Ada.Text_IO.New_Line; Geometry.IO.Get (Item => MyFig); Geometry.IO.Put (Item => MyFig); END LOOP; END Test_Geometry;Sample Run
Trial #1 Enter a shape: rectangle, circle, square > triangle Value not a valid shape. Please try again. Enter a shape: rectangle, circle, square > rect Value not a valid shape. Please try again. Enter a shape: rectangle, circle, square > rectangle Enter width. Enter a floating-point value between 0.00 and 1000000.00 > 3 Enter height. Enter a floating-point value between 0.00 and 1000000.00 > 5 Figure shape: RECTANGLE height = 5.00; width = 3.00; perimeter = 16.00; area = 15.00 Trial #2 Enter a shape: rectangle, circle, square > circle Enter circle radius. Enter a floating-point value between 0.00 and 1000000.00 > 4 Figure shape: CIRCLE radius = 4.00; perimeter = 25.13; area = 50.27 Trial #3 Enter a shape: rectangle, circle, square > square Enter length of side. Enter a floating-point value between 0.00 and 1000000.00 > 5 Figure shape: SQUARE side = 5.00; perimeter = 20.00; area = 25.00
RightTriangle : (Base, Height : Real);to
Figure
and modify the operators to include
triangles. Use the formulas
area = 1/2 base × height _______________ hypotenuse = \/base2 + height2
where base and height are the two sides that form the right angle.
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.