Ada: OOP
Object-Oriented Principles
The object-oriented paradigm is based on a few key principles in computer science:
Encapsulation
Encapsulation refers to the bundling of data with the methods that operate on that data. This allows objects to hide their internal representation, exposing only an interface.
Encapsulation provides the following benefits:
Reduced coupling between classes
Increased modularity
Better code reuse
Information hiding
Encapsulation is achieved in object-oriented languages through the use of access modifiers like public
, private
, and protected
.
Inheritance
Inheritance allows classes to inherit data and behavior from other classes. This allows for code reuse and extensibility.
Inheritance creates an "is-a" relationship between classes.
Inheritance provides the following benefits:
Code reuse
Extensibility
Easy maintenance
Polymorphism
Polymorphism means "many forms" and refers to the ability of an object to take on many forms.
There are two main types of polymorphism:
Compile time polymorphism - achieved using method overloading
Runtime polymorphism - achieved using method overriding
Polymorphism allows us to perform the same operation in different ways depending on the input. This makes code more flexible and reusable.
Abstraction
Abstraction refers to the process of hiding unnecessary details and exposing only relevant information.
Classes and interfaces allow us to achieve abstraction by hiding their implementation details and exposing only their public methods.
Abstraction helps manage complexity by hiding irrelevant details.
OOP in Ada
Ada supports object-oriented programming (OOP) using tagged types and class-wide types. Some key concepts:
Tagged Types
Tagged types are Ada's equivalent of classes. They allow:
Type derivation - You can derive new tagged types from an existing tagged type.
Subtyping - Derived tagged types are subtypes of the parent tagged type.
Runtime polymorphism - Methods can be overridden in derived types and dispatched dynamically based on the exact type of the object.
Encapsulation - Tagged types can hide their data.
You declare a tagged type using the tagged
keyword:
type My_Class is tagged null record;
And derive a new tagged type using the new
keyword:
type Derived is new My_Class with record
A : Integer;
end record;
Methods are procedures or functions that take the tagged type as their first parameter:
procedure Foo (Self : in out My_Class);
overriding
procedure Foo (Self : in out Derived);
The overriding
keyword indicates a method override.
Class-wide Types
Class-wide types allow a variable to hold an object of a tagged type or any of its descendants. They are declared using the 'Class
notation:
Object3 : My_Class'Class := Object2;
This allows for runtime polymorphism and method dispatching based on the actual type of the object.
You can call methods using dot notation:
Object3.Foo; -- Dispatches to Derived.Foo or My_Class.Foo
In Summary
Tagged types and class-wide types provide Ada's support for object-oriented programming, allowing type derivation, subtyping, runtime polymorphism and method dispatching. The syntax is slightly different from mainstream OOP languages but the concepts are the same.
Compare to Java OOP
Ada and Java both support object-oriented programming, but there are some key differences:
Tagged Types vs Classes
In Ada, objects are defined using tagged types, while in Java objects are defined using classes.
Tagged types in Ada are similar to classes in that they support:
Type derivation
Inheritance of primitive operations
Method overriding
Dynamic dispatch
However, tagged types in Ada are still just record types under the hood. They don't have the full-class functionality of Java.
No Access Modifiers
Ada does not have access modifiers like public
, private
and protected
to control visibility. Instead, Ada uses packages to achieve encapsulation.
Methods in Ada are declared outside the tagged type, while in Java they are declared within the class.
No Constructors and Destructors
Ada does not have traditional constructors like Java or C++. However, it supports a similar mechanism using tagged types and functions that return tagged types.
Here is how you can implement a "constructor" in Ada:
- Define a tagged type:
type My_Type is tagged record
...
end record;
- Create a function that returns the tagged type:
function Create (arg1, arg2: Type) return My_Type is
begin
return ( ... ); -- Initialize record fields
end Create;
- Call the function to "construct" an object:
Obj : My_Type := Create(1.0, 2.0);
Any function that returns a tagged type can effectively act as a constructor. The function initializes the record fields and returns the tagged type object.
Ada also supports controlled types that implement Initialize
and Finalize
procedures. These can be used to perform initialization and finalization of objects.
For example:
type My_Controlled is new Controlled with record
...
end record;
procedure Initialize (Obj : in out My_Controlled) is
begin
... -- Initialization code
end Initialize;
function Create (arg1, arg2: Type) return My_Controlled is
Obj : My_Controlled;
begin
Obj := (...); -- Initialize fields
Initialize (Obj);
return Obj;
end Create;
Here, the Initialize
procedure is called as part of constructing the object.
More Static Typing
Java has a more dynamic type system compared to Ada. For example, in Java you can assign an object of a subclass to a superclass reference. In Ada, this requires a view conversion.
In general, Ada's type system is more strict and relies more on static type checking.
So in summary, while Ada and Java both support object-oriented concepts like inheritance, polymorphism and encapsulation, they implement them in slightly different ways. Ada's approach is more static, while Java's is more dynamic. But both languages effectively allow you to model real-world entities as software objects.
Inheritance in Ada
Inheritance in Ada works similarly to other object-oriented languages like C++ and Java, though with some syntactic differences. Some key points:
Tagged Types
Ada implements inheritance using tagged types. A tagged type is like a class in other languages. You declare a tagged type using the tagged
keyword:
type Parent is tagged record
...
end record;
You derive a new tagged type using the new
keyword:
type Child is new Parent with record
...
end record;
This creates an is-a
relationship where Child
inherits from Parent
.
Primitive Subprograms
Subprograms that are eligible for inheritance are called primitive subprograms. They are defined by having at least one parameter of the tagged type or by returning a result of the tagged type.
Primitive subprograms are declared outside the tagged type, but in the same scope. They take a controlling parameter of the tagged type as the first parameter:
procedure Method (Self : Parent; ...)
Method Overriding
Child types can override the methods of their parent types using the overriding
keyword:
overriding
procedure Method (Self : Child; ...)
Dynamic Dispatch
Calls to primitive subprograms on tagged types are dynamically dispatched. This means the method of the actual object type will be called, not the static type.
Ada differentiates between references to a specific tagged type and references to a tagged type hierarchy using class-wide types. Calls using class-wide types are not overridden.
Dot Notation
Ada supports a dot notation to call primitive subprograms, if the dispatching parameter is the first parameter.
In Summary
Inheritance in Ada is implemented using tagged types. Primitive subprograms defined on tagged types are eligible for inheritance and overriding. Calls to these subprograms use dynamic dispatch to select the appropriate overridden method.
Multiple Inheritance
Ada supports both single and multiple inheritance while Java only supports single inheritance.
Ada does not have a default base class that new classes inherit from. Any type can serve as a parent type for inheritance. While Java has the Object class as the default base class that all classes ultimately inherit from.
In Ada, a derived type can be inherited from one or more parent types. This allows Ada to support multiple inheritance.
For example in Ada:
type A is ...
type B is ...
type C is new A and B with ...
Here type C inherits from both A and B, demonstrating multiple inheritance.
In contrast, in Java a class can only extend from one superclass using the "extends" keyword. This restricts Java to only support single inheritance.
For example in Java:class A { ... }
class B { ... }
class C extends A { ... }
Here class C extends only class A, demonstrating single inheritance in Java.
The main reason Java only supports single inheritance is to avoid the complexity and potential problems that can arise with multiple inheritance. Some of these issues are:
Diamond problem: When a subclass inherits from two classes that both inherit from a common superclass.
Inheritance conflicts: When two parent classes define methods with the same signature.
By only allowing single inheritance, Java keeps the inheritance model simpler and avoids these potential problems. However, this also limits the flexibility of the language.
Ada Code Snippet
Here is the code example for multiple inheritance in Ada:
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
type Parent_1 is interface;
procedure Procedure_1 (Self : in out Parent_1) is abstract;
type Parent_2 is interface;
procedure Procedure_2 (Self : in out Parent_2) is abstract;
type Child is new Parent_1 and Parent_2 with null record;
procedure Procedure_1 (Self : in out Child) is
begin
Put_Line ("Procedure_1 called");
end Procedure_1;
procedure Procedure_2 (Self : in out Child) is
begin
Put_Line ("Procedure_2 called");
end Procedure_2;
C : Child;
begin
C.Procedure_1;
C.Procedure_2;
end Main;
Explanation:
Declare Parent_1 and Parent_2 as interfaces with abstract procedures
Child inherits from both parent interfaces using
and
Override the procedures in Child
Create a Child object C
Call the inherited procedures Procedure_1 and Procedure_2 on C
This demonstrates multiple inheritance in Ada by having Child inherit from two parent interface types.
OOP Summary
Ada supports object-oriented programming through the use of tagged types. Tagged types allow you to:
Derive new types from existing types to extend and reuse their functionality
Define methods (primitives) that dispatch dynamically based on the actual type of the object
Use class-wide types that can refer to objects of a type or any of its descendants
Override methods in derived types to customize their behavior
The Ada model of OOP has some differences compared to languages like Java and C++:
Encapsulation is implemented at the package level, not the type level
Tagged types are extended by deriving new record types
Methods are defined separately from the tagged type definition
Class-wide types are used to achieve polymorphism
Dispatching occurs only for class-wide types, not specific types
Ada has interfaces in addition to tagged types
Some key concepts in OOP for Ada:
Tagged types - Types marked with the
tagged
keywordPrimitives - Subprograms that are methods of a tagged type
Overriding - Redefining a primitive in a derived type
Class-wide types - Types that refer to a tagged type or any of its descendants
Dispatching calls - Calls that invoke the appropriate overridden primitive based on the actual type
Type invariants - Properties that must hold after calls to primitives
You can learn about object-oriented programming for Ada from:
Disclaim: This article was created using Rix in multiple modes with several prompts created by myself. I will get to the bottom of this and will create a new article on my website. Feel free to comment below or ask a question.
Subscribe to my newsletter
Read articles from Elucian Moise directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Elucian Moise
Elucian Moise
Software engineer instructor, software developer and community leader. Computer enthusiast and experienced programmer. Born in Romania, living in US.