Ada: Object Orientation
This text provides an introduction to object orientation in Ada. We introduce a programming style that allows for the same flexibility we expect from object systems in C++, Java, or CLOS. In particular, we expect an object system (i) to allow for polymorphism, (ii) to shrink and grow its memory footprint as needed, (iii) to allow arbitrary composition, and (iv) to play well with standard data structures, e.g., strings, lists, or maps.
Interfaces
The first pillar of an object-oriented programming style is that we separate interfaces from implementations.
Definition
We define an interface by introducing its type, access type, and deallocation procedure.
-- as.ads
with Ada.Unchecked_Deallocation;
package As is
type A is limited interface;
type A_Access is access A'Class;
procedure Free_A is new Ada.Unchecked_Deallocation
(Object => A'Class,
Name => A_Access);
procedure Free (E : in out A_Access)
renames Free_A;
end As;
When introducing an interface, we declare it limited, since we will reference objects instead of copying them. Note, that we need an access type onto A'Class instead of just A since A is abstract and, thus, cannot be constructed directly. Moreover, whenever we introduce an interface, we also introduce a way to deallocate it.
Methods
We introduce an interface’s methods as abstract basic operations.
function Is_Ready (Self : A) return Boolean is abstract;
Package Structure
We separate interfaces from the classes implementing them by defining the class in a child package of the interface.
E.g., we might define the interface A in the package As stored in as.ads. Then we would give the implementing class Simple_A in the package As.Simple stored in as-simple.ads.
Classes
We define a class by extending its interface type.
-- as-simple.ads
package As.Simple is
type Simple_A is new A with private;
overriding
function Is_Ready (Self : Simple_A) return Boolean;
private
type Simple_A is new A with
record
Ready : Boolean;
end record;
end As.Simple;
We give the class fields only in the package’s private part to force clients to access objects via it’s methods. We mark method implementations overriding to underpin their relation to the inherited interface.
Constructors
Constructors are ways to instantiate interfaces. Generally a constructor is a function that returns a reference to an object allocated on the heap. In general, we are inclined to also provide a copy-constructor.
function Make_A (Ready : Boolean) return not null A_Access is
(new Simple_A'(Ready => Ready));
function Copy_A (Other : not null A_Access) return not null A_Access is
(Make_A (Other.Is_Ready));
Composition
One of the most important patterns in object-oriented design is the composite pattern. When nesting classes we demand that an object finalizes all the objects it owns. Ada’s controlled types allow us to do that. However, controlled types complicate interface definition a tad bit.
-- as.ads
with Ada.Finalization; use Ada.Finalization
with Ada.Unchecked_Deallocation;
package As is
type A is abstract new Limited_Controlled with null record;
type A_Access is access A'Class;
procedure Free_A is new Ada.Unchecked_Deallocation
(Object => A'Class,
Name => A_Access);
procedure Free (E : in out A_Access)
renames Free_A;
end As;
-- as-r.ads
package As.R is
type R is new A with private;
function Make_R return not null A_Access;
private
type R is new A with null record;
end As.R;
-- as-r.adb
package body As.R is
function Make_R return not null A_Access is
(new R'(Limited_Controlled with null record));
end As.R;
-- as-s.ads
package As.S is
type S is new A with private;
overriding
procedure Finalize (E : in out S);
function Make_S (X : not null A_Access) return not null A_Access;
private
type S is new A with
record
Child : A_Access;
end record;
end As.S;
Note
Intuitively, we would like to declare S’s field Child to be not null. However, if we did that, we would not be able to call Free on it, since it sets the field null to safeguard against dangling pointers.
-- as-s.adb
package body As.S is
overriding
procedure Finalize (E : in out S) is
begin
Finalize (Limited_Controlled (E));
Free (E.Child);
end Finalize;
function Make_S (X : not null A_Access) return not null A_Access is
(new S'(Limited_Controlled with Child => X));
end As.S;
Common Data Structures
Strings
Often, we need to manage strings as part of objects. We manage strings in exactly the way shown in the section on composition. Herein, we can make use of the type Ada.Strings.Unbounded.String_Access and Ada.Strings.Unbounded.Free.