Ada Programming/Object Orientation
Object-orientation on Ada
An Ada class consists of three building blocks:
- a package
- a record (data storage)
- primitive operations (methods).
This is different from C++ which has no concept of a package.
Note: Rigorously the meaning of the term class is different in Ada than in C++, Java and others. In Ada, a class is a special type whose values are those of the union of all the types derived from a given tagged type, called the class-wide type. In C++, the term class is a bit polysemic, it does mean the former concept, and it is a concrete type with operations associated, and finally a construct to define them.
The package
Every class has to be defined inside a package. One package may contain more than one class. You might be surprised about that requirement but you should remember that every class needs some housekeeping information which has to be kept somewhere; in Ada this housekeeping information is part of the package.
A little note for C++ programmers
If you have ever, when linking a C++ program, got an error message telling you that the virtual function table for class X was not found then you might appreciate this restriction. In Ada you never get this error message - the Ada equivalent of the virtual function table is part of the package. The same goes for the Ada equivalent of a virtual inline function, another cause of trouble when linking C++ programs - and they too work flawlessly in Ada.
The tagged record
A tagged type provides support for dynamic polymorphism and type extension in Ada. A tagged type bears a hidden tag that identifies the type and its position in the inheritance tree at run-time. It also provides the means of storing instance data.
package Person is
type Object is tagged
record
Name : String (1 .. 10);
Gender : Gender_Type;
end record;
end Person;
with Person;
package Programmer is
type Object is new Person.Object with
record
Skilled_In : Language_List;
end record;
end Programmer;
The class-wide type
Whenever you declare a tagged type, the compiler will also provide a Class type for you. This type is called a class-wide type. Beginners often consider the class-wide type as some abstract concept. But this is not true! The Class type is a real data type. You can declare variables of the class-wide type, assign values to them, use the class-wide type as a parameter to functions and procedures and so forth.
The interesting part is that the class-wide type can hold values of any child class of the declared base type; in fact, there are no objects of this class-wide type - an object always is of a specific child type. This means that, unlike with most other object-oriented programming languages, you do not need to resort to the use of heap memory for storing arbitrary members of a class family but you can use the Class type instead.
Since the size of the child class is not known at compile time, any class-wide type is an indefinite subtype.
Primitive operations
The primitive operations of a given tagged type are those having a parameter or return value of this type and are declared immediately in the same package as the type.
Primitive operations are inherited and can be overridden for derived types.
Examples:
package X is
type Object is tagged null record;
procedure Class_Member_1 (This : in Object);
procedure Class_Member_2 (This : out Object);
procedure Class_Member_3 (This : in out Object);
procedure Class_Member_4 (This : access Object);
function Class_Member_5 return Object;
end X;
A note for C++ programers: Class types are "by reference types" - they are always passed to a subprogram by using a pointer and never use "call by value" - using an access explicitly is therefore seldom needed.
Please note that neither named access types nor class-wide types qualify as class members. Procedures and functions having those as a parameter are just normal subprograms sharing the same package without belonging to the class.
Examples:
package X is
type Object is tagged null record;
type Object_Access is access Object;
type Object_Class_Access is access Object'Class;
procedure No_Class_Member_1 (This : in Object'Class);
procedure No_Class_Member_2 (This : in out Object_Access);
procedure No_Class_Member_2 (This : out Object_Class_Access);
function No_Class_Member_4 return Object'Class;
package Inner is
procedure No_Class_Member_5 (This : in Object);
end Inner;
end X;
Note for C++ programmers: Procedures and functions like these can serve the same purpose as "non virtual" and "static" methods have in C++.
This language feature will be made available in the forthcoming Ada 2005 standard.
The keyword overriding can be used to indicate whether an operation is overriding an inherited subprogram. For example:
package X is
type Object is tagged null record;
procedure Class_Member_1 (This : in Object);
procedure Class_Member_2 (This : in out Object);
function Class_Member_3 return Object;
type Derived_Object is new Object with null record;
overriding
procedure Class_Member_1 (This : in Derived_Object);
-- 'Class_Member_2' is not overridden
overriding
function Class_Member_3 return Derived_Object;
not overriding
function New_Class_Member_1 return Derived_Object;
end X;
The compiler will check the desired behaviour.
This is a good programming practice because it avoids some nasty bugs like not overriding an inherited subprogram because the programmer spelt the identifier incorrectly, or because a new parameter is added later in the parent type.
It can also be used with abstract operations, with renamings, or when instantiating a generic subprogram:
not overriding
procedure Class_Member_1 (This : in Object) is abstract;
overriding
function Class_Member_2 return Object renames Old_Class_Member;
not overriding
procedure Class_Member_3 (This : out Object)
is new Generic_Procedure (Element => Integer);
Interfaces
This language feature will be made available in the forthcoming Ada 2005 standard.
Interfaces allow for a limited form of multiple inheritance. On a sematic level they are similar to an "abstract tagged null record" as they may have primitive operations but cannot hold any data. These operations cannot have a body, so they are either declared abstract or null. Abstract means the operation has to be overridden, null means the default implementation is a null body, i.e. one that does nothing.
An interface is declared with:
package Printable is
type Object is interface;
procedure Class_Member_1 (This : in Object) is abstract;
procedure Class_Member_2 (This : out Object) is null;
end Printable;
You implement an interface by adding it to a concrete class:
with Person;
package Programmer is
type Object is new Person.Object
and Printable.Object
with
record
Skilled_In : Language_List;
end record;
overriding
procedure Class_Member_1 (This : in Object);
not overriding
procedure New_Class_Member (This : Object; That : String);
end Programmer;
As usual, all inherited abstract operations must be overriden altough null subprograms ones need not.
Class names
Both the class package and the class record need a name. In theory they may have the same name, but in practice this leads to nasty (because of unintutive error messages) name clashes when you use the use clause. So over time three de facto naming standards have been commonly used.
Classes/Class
The package is named by a plural noun and the record is named by the corresponding singular form.
package Persons is
type Person is tagged
record
Name : String (1 .. 10);
Gender : Gender_Type;
end record;
end Persons;
This convention is the usually used in Ada's built-in libraries.
Disadvantage: Some "multiples" are tricky to spell, especially for those of us who aren't native English speakers.
Class/Object
The package is named after the class, the record is just named Object.
package Person is
type Object is tagged
record
Name : String (1 .. 10);
Gender : Gender_Type;
end record;
end Person;
Most UML and IDL code generators use this technique.
Disadvantage: You can't use the use clause on more than one such class packages at any one time. However you can always use the "type" instead of the package.
Class/Class_Type
The package is named after the class, the record is postfixed with _Type.
package Person is
type Person_Type is tagged
record
Name : String (1 .. 10);
Gender : Gender_Type;
end record;
end Person;
Disadvantage: lots of ugly "_Type" postfixes.
See also
Wikibook
Wikipedia
Ada Reference Manual
Ada 95
Ada 2005
Ada Quality and Style Guide
|