To produce "good" Ada95 code using Object Oriented methodology is certainly not obvious. Despite the comprehensive documentation available, there are many pitfalls along the way. This document is the fruit of the experience gained through wanderings, tries and failures.
There is one problem with people who know everything:
They don't learn anything !
Anonymous
with Angle;
package Coordinates is
type Object is ... -- Class type (either private or derived with private extension).
type Geographic is record -- Regular type (public)
Latitude : Angle.Radian;
Longitude : Angle.Radian;
end record;
end Coordinates;
That is the basics, but still, the programmer has many choices: Tagged ? Controlled ? ...
Let's discuss the details further:
with Scalar;
package Angle is
type Object is private;
Nil : constant Object;
type Radian is new Scalar.Float_15;
type Degree is new Scalar.Float_15;
function Radian_Of(The_Angle : in Angle.Object) return Angle.Radian;
function From_Radian(The_Radian : in Angle.Radian) return Angle.Object;
function Degree_Of(The_Angle : in Angle.Object) return Angle.Degree;
function From_Degree(The_Degree : in Angle.Degree) return Angle.Object;
function "+"(Left, Right : in Angle.Object) return Angle.Object;
...
private
type Object is record
In_Radian : Angle.Radian := Angle.Radian'Last;
end record;
Nil : constant Angle.Object := (In_Radian => Angle.Radian'Last);
end Angle;
with Ada.Finalization;
with Color, Engine, Speed;
package Car is
type Object is new Ada.Finalization.Controlled with private;
type Reference is access all Object'Class;
type View is access constant Object'Class;
Nil : constant Object;
type Size is (Small, Normal, Big, Unknown);
subtype Valid_Size is Size range Small .. Big;
procedure Initialize(The_Car : in out Car.Object); -- Optional
procedure Adjust(The_Car : in out Car.Object); -- Optional
procedure Finalize(The_Car : in out Car.Object); -- Optional
procedure Initialize(The_Car : in out Car.Object'Class; -- Wide class
With_Color : in Color.Object;
With_Size : in Car.Valid_Size;
With_Engine : in Engine.Reference);
procedure Set_Color(To : in Color.Object;
Affecting : in out Car.Object);
procedure Set_Size(To : in Car.Valid_Size;
Affecting : in out Car.Object);
function Color_Of(The_Car : in Car.Object) return Color.Object;
function Speed_Of(The_Car : in Car.Object) return Speed.Object;
private
type Object is new Ada.Finalization.Controlled with record
Size : Car.Size := Unknown;
Own_Color : Color.Object := Color.Nil;
Own_Engine : Engine.Reference := null;
...
end record;
Nil : constant Car.Object
:= (Ada.Finalization.Controlled with
Size => Unknown,
Own_Color => Color.Nil,
Own_Engine => null,
...);
end Car;
package body Car is
procedure Initialize(The_Car : in out Car.Object'Class;
With_Color : in Color.Object;
With_Size : in Car.Valid_Size;
With_Engine : in Engine.Reference) is
begin
-- Dispatching call, if car is derived, the child's 'Set_Color' is called.
--
Set_Color(To => With_Color,
Affecting => The_Car);
-- Non-dispatching call, 'Car.Set_Size' is always called.
--
Set_Size(To => With_Size,
Affecting => Car.Object(The_Car));
The_Car.Engine := With_Engine;
end;
...
end Car;
package Angle is type Object is private; type Reference is access all Object'Class; type View is access constant Object'Class; Nil : constant Object; ... end Angle; generic Item ... package List is type Object is new Ada.Finalization.Controlled with private; type Reference is access all Object'Class; type View is access constant Object'Class; Nil : constant Object; ... end List;
generic ... package List_Pkg is type List is new ... end List_Pkg; package Fruit_List_Pkg is new List_Pkg(...);The name of Fruit_List main type is not Fruit_List_Pkg.Fruit_List as one would reasonably expect, but a plain Fruit_List_Pkg.List
Insert(The_Item => An_Apple,
Before_Index => 3,
Into => The_Fruit_List);
Merge(Left => My_Fruit_List,
Right => The_Citrus_Fruits);
Is_Empty(The_Queue); -- Positional association used.
package List is
...
Pop(The_Item : out List.Item;
From_List : in out List.Object); -- Use of From_List instead
-- of From.
end List;
package Queue is
...
type Object is new List.Object; -- Queue.Object inherits
-- List's operations.
end;
procedure Main is
begin
...
Queue.Pop(
The_Item => My_Item,
From_List => The_Queue); -- Not very clear, the name specifies
-- a list but the argument is a Queue ?!
end;
package Car is
type Plate_Number is ...
-- The Initialize routine doesn't check if the plate number
-- is duplicated.
--
procedure Initialize(
The_Car : in out Car.Object'Class;
With_Plate : in Car.Plate_Number);
... );
...
end Car;
-- A collection is defined.
--
package Car_Table is new Table(
Item => Car.Reference,
Id => Car.Plate_Number,
...);
procedure Main is
My_Car : Car.Reference := new Car.Object;
...
begin
...
Car.Initialize(The_Car => My_Car.all,
With_Plate => My_Plate,
...);
Car_Table.Add(
The_Item => My_Car,
Into => The_Car_Table,
Using_Policy => Raise_Exception); -- An exception is raised if
-- the plate number is
-- duplicated.
...
end;
package Scalar is type Integer_8 is range -2**7 .. 2**7-1; subtype Natural_8 is Integer_8 range 0 .. 2**7-1; subtype Positive_8 is Integer_8 range 1 .. 2**7-1; for Integer_8'Size use 8; type Integer_16 is range -2**15 .. 2**15-1; subtype Natural_16 is Integer_16 range 0 .. 2**15-1; subtype Positive_16 is Integer_16 range 1 .. 2**15-1; for Integer_16'Size use 16; type Integer_32 is range -2**31 .. 2**31-1; subtype Natural_32 is Integer_32 range 0 .. 2**31-1; subtype Positive_32 is Integer_32 range 1 .. 2**31-1; for Integer_32'Size use 32; type Float_6 is digits 6; for Float_6'Size use 32; type Float_15 is digits 15; for Float_15'Size use 64; end Scalar;
with List, Employee; package Employee_List is new List(Item => Employee.Object);Later (incremental process), the method Search is added to the class Employee_List.
with List, Employee;
package Employee_List_Instance is new List(Item => Employee.Object);
with Employee_List_Instance, Personne, Employee;
package Employee_List is
type Object is new Employee_List_Instance.Object with private;
type Reference is access all Object'Class;
type View is access constant Object'Class;
Nil : constant Object;
...
-- Rename all regular types and exceptions of Employee_List_Instance.
--
First_Of_Empty_List_Error : exception
renames Employee_List_Instance.First_Of_Empty_List_Error;
...
subtype Length is Employee_List_Instance.Length;
...
-- Add the new method.
--
Search_Not_Found_Error : exception;
procedure Search(The_Employee : out Employee.View;
With_Name : in Personne.Name;
Within : in Employee_List.Object);
private
...
end Employee_List;
package Coordinates is
type Object is tagged private;
...
function Distance_Between(Left : in Coordinates.Object;
Right: in Coordinates.Object)
return Distance.Object;
...
end Coordinates;
package Position is
type Object is new Coordinates.Object with private;
...
function Distance_Between(Left : in Position.Object; -- Overload of
Right : in Position.Object) -- Coordinates.Distance_Between
return Distance.Object;
...
end Position;
generic
type Item is private;
package List is
type Object is ...
...
type Action is access procedure(
The_Item : in out Item;
Stop_Iterating : out Boolean);
procedure Iterate(
The_Action : Action;
Through : in List.Object);
...
end List;
with Student;
package Student_List is new List(Item => Student.Object);
function Retrieve_Student(With_Name : Student.Name;
From : Student_List.Object) return Student.Object is
The_Student_Found : Student.Object;
procedure Find(The_Student : in out Student.Object;
Stop_Iterating : out Boolean) is
begin
if Student.Name_Of(The_Student) = With_Name then
The_Student_Found := The_Student;
Stop_Iterating := True;
else
Stop_Iterating := False;
end if;
end;
begin
Student_List.Iterate(The_Action => Find'Access, -- Compilation Error
Through => From);
return The_Student_Found;
end;
It is interesting to note that Ada 95 doesn't provide 'Unchecked_Access for procedures (as it does for variables). That would have solved our problem. The GNAT compiler provides 'Unrestricted_Access which resolves this shortcoming, but also creates a compiler dependency.
Here is an alternative solution using generic.generic
type Item is private;
package List is
...
generic with procedure Action(
The_Item : in out Item;
Stop_Iterating : out Boolean);
procedure Iterate (Through : in List.Object'Class); -- Wide class nescessary,
-- parameterized methods are
-- not inheritable.
...
end List;