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.

Lists

Maps