Ada: OOP

Ada: OOP

Ada OOP - object oriented programming

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:

  1. Define a tagged type:
type My_Type is tagged record 
   ...
end record;
  1. Create a function that returns the tagged type:
function Create (arg1, arg2: Type) return My_Type is
begin
   return ( ... );  -- Initialize record fields  
end Create;
  1. 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 keyword

  • Primitives - 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.