Welcome to a comprehensive comparison of Object-Oriented Programming (OOP) across three distinct programming languages: Java, C++, and Ada. This document explores how each language approaches the fundamental concepts of OOP and provides practical examples using the GNAT (GNU Ada) Compiler. By understanding these comparisons, you'll develop a deeper appreciation for language design decisions and be better equipped to choose the right tool for your projects.
❓ What is Object-Oriented Programming?
Object-Oriented Programming is a paradigm that organizes code around "objects" that contain both data (variables) and functions (methods). Think of an object like a blueprint: a Drink class defines what properties all drinks have and what operations can be performed on them.
The core principles of OOP are:
- Encapsulation: Bundling data and methods together, hiding internal details
- Inheritance: Creating new classes based on existing ones
- Polymorphism: Objects of different classes responding to the same message in different ways
- Abstraction: Showing only essential features while hiding complexity
📊 Language Feature Comparison Table
| Feature | Java | C++ | Ada |
|---|---|---|---|
| Class Definition | class ClassName |
class ClassName |
type T is tagged record |
| Constructor | ClassName() |
ClassName() |
function Create return T |
| Destructor | Automatic (Garbage Collected) | ~ClassName() |
procedure Finalize (Controlled types) |
| Inheritance | extends |
: |
type T is new Parent with record |
| Access Modifiers | public, private, protected |
public, private, protected |
private section in package spec |
| Method Override | @Override annotation |
virtual keyword |
overriding keyword |
| Polymorphism | Virtual by default | Must use virtual |
Dispatching on tagged types |
| Memory Management | Automatic (Garbage Collection) | Manual (new/delete) |
Manual or Controlled types |
| Interfaces | interface keyword |
Pure virtual classes | interface keyword (Ada 2005+) |
| Constants | final keyword |
const keyword |
constant keyword |
| Compilation | javac |
g++ |
gnatmake |
✏️ Example 1: Basic Class Definition
Let's compare how to define a basic class in all three languages:
public class Drink {
final int MAXIMUM = 10;
private String name;
public Drink(String brandName) {
name = brandName;
}
public String getName() {
return name;
}
}
class Drink {
private:
static const int MAXIMUM = 10;
string name;
public:
Drink(string brandName) {
name = brandName;
}
string getName() {
return name;
}
};
-- Package Specification (drink_package.ads)
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
package Drink_Package is
type Drink is tagged private;
function Create(Brand_Name : String) return Drink;
function Get_Name(D : Drink) return String;
private
Maximum : constant Integer := 10;
type Drink is tagged record
Name : Unbounded_String := Null_Unbounded_String;
end record;
end Drink_Package;
🔍 Key Observations:
- Java: Requires access modifiers on each method; uses
finalfor constants - C++: Uses separate
private:andpublic:sections; usesstatic constfor class constants - Ada: Uses
tagged recordfor OOP types; separates specification (.ads) from body (.adb); usesprivatesection for encapsulation; constants declared withconstantkeyword
✏️ Example 2: Constructor Overloading
All three languages support multiple constructors with different parameter lists:
public Drink(String name) {
this.name = name;
this.cost = 1.75;
}
public Drink(String name, double cost) {
this.name = name;
this.cost = cost;
}
Drink(string name) {
this->name = name;
this->cost = 1.75;
}
Drink(string name, double cost) {
this->name = name;
this->cost = cost;
}
function Create(Brand_Name : String) return Drink is
D : Drink;
begin
D.Name := To_Unbounded_String(Brand_Name);
D.Cost := 1.75;
return D;
end Create;
function Create(Brand_Name : String; Cost : Float) return Drink is
D : Drink;
begin
D.Name := To_Unbounded_String(Brand_Name);
D.Cost := Cost;
return D;
end Create;
🔍 Key Observations:
- Java: Uses
this.variableto reference instance variables - C++: Uses
this->variablepointer notation - Ada: Uses functions that return the type; explicit record field assignment; uses
:=for assignment; overloading based on parameter signatures
✏️ Example 3: Methods and Encapsulation
Here's how methods work in each language, using the vending machine example:
public void vend() {
if (available > 0) {
available -= 1;
consumed += 1;
moneyMade += cost;
System.out.println(name + " Purchased.");
}
}
public double profit() {
return moneyMade;
}
void vend() {
if (available > 0) {
available -= 1;
consumed += 1;
moneyMade += cost;
cout << name << " Purchased." << endl;
}
}
double profit() {
return moneyMade;
}
procedure Vend(D : in out Drink) is
begin
if D.Available > 0 then
D.Available := D.Available - 1;
D.Consumed := D.Consumed + 1;
D.Money_Made := D.Money_Made + D.Drink_Cost;
Put_Line(To_String(D.Name) & " Purchased.");
end if;
end Vend;
function Profit(D : Drink) return Float is
begin
return D.Money_Made;
end Profit;
🔍 Key Observations:
- Java: Uses
System.out.println()for output - C++: Uses
coutandendlfrom iostream - Ada: Uses
Put_Line()for output (fromAda.Text_IO); uses&for string concatenation; usesprocedurefor methods that modify state (in outparameter) andfunctionfor methods that return values; explicit parameter passing mode
🔧 The Complete Vending Machine Example
Now let's look at a complete, working example using the Ada language and the GNAT (GNU Ada) Compiler. This program implements a simple vending machine that tracks drink sales and profits using separate package specification and body files.
⚠️ Important: Packages and the with Clause in Ada
When using Ada with separate files, you organize code into packages. Each package has a specification (.ads) containing public declarations and a body (.adb) containing the implementation. Other files include packages with the with clause.
✅ The Correct Approach
Create a package specification (drink_package.ads) with type and subprogram declarations, a package body (drink_package.adb) with implementations, and a main procedure file (machine.adb) that uses that package.
Why Packages Are Essential for Ada
The package system in Ada is crucial because:
- Separates Specification from Body: The
.adsfile declares what's public; the.adbfile contains the actual code - Enables Strong Encapsulation: The
privatesection hides implementation details from clients - Clear Dependencies: The
withclause explicitly shows what packages your code depends on - Prevents Circular Dependencies: Ada's package system enforces a clear dependency hierarchy
- Supports Separate Compilation: Packages can be compiled independently and reused across projects
💡 How It Works
When you write with Drink_Package;, the compiler looks for drink_package.ads, reads its specification, and makes those declarations available to your program. The use Drink_Package; clause makes the names directly visible without qualification.
drink_package.ads - The Package Specification
-- drink_package.ads
-- Package Specification for the Drink class
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
package Drink_Package is
type Drink is tagged private;
-- Constructors
function Create(Brand_Name : String) return Drink;
function Create(Brand_Name : String; Cost : Float) return Drink;
-- Methods
procedure Vend(D : in out Drink);
procedure Refill(D : in out Drink);
function Profit(D : Drink) return Float;
function Drinks_Sold(D : Drink) return Integer;
function Get_Drink_Name(D : Drink) return String;
function Restock_Amount(D : Drink) return Integer;
private
-- Constants
Maximum_Capacity : constant Integer := 10;
Default_Drink_Cost : constant Float := 1.75;
-- Private tagged record definition
type Drink is tagged record
Available : Integer := 0;
Consumed : Integer := 0;
Use_Default_Cost : Integer := 1;
Use_Supplied_Cost : Integer := 0;
Drink_Cost : Float := Default_Drink_Cost;
Money_Made : Float := 0.0;
Name : Unbounded_String := Null_Unbounded_String;
end record;
end Drink_Package;
drink_package.adb - The Package Body
-- drink_package.adb
-- Package Body for the Drink class
with Ada.Text_IO; use Ada.Text_IO;
package body Drink_Package is
-- Create (1) --
function Create(Brand_Name : String) return Drink is
D : Drink;
begin
D.Name := To_Unbounded_String(Brand_Name);
D.Consumed := 0;
D.Available := 0;
D.Money_Made := 0.0;
D.Drink_Cost := Default_Drink_Cost;
D.Use_Default_Cost := 1;
D.Use_Supplied_Cost := 0;
return D;
end Create;
-- Create (2) --
function Create(Brand_Name : String; Cost : Float) return Drink is
D : Drink;
begin
D.Name := To_Unbounded_String(Brand_Name);
D.Consumed := 0;
D.Available := 0;
D.Money_Made := 0.0;
D.Drink_Cost := Cost;
D.Use_Default_Cost := 0;
D.Use_Supplied_Cost := 1;
return D;
end Create;
-- Vend --
procedure Vend(D : in out Drink) is
begin
if D.Available > 0 then
D.Available := D.Available - 1;
D.Consumed := D.Consumed + 1;
D.Money_Made := D.Money_Made +
(Float(D.Use_Default_Cost) * Default_Drink_Cost +
Float(D.Use_Supplied_Cost) * D.Drink_Cost);
Put_Line(To_String(D.Name) & " Purchased.");
else
Put_Line("");
Put_Line(" SOLD OUT ");
Put_Line("");
end if;
end Vend;
-- Refill --
procedure Refill(D : in out Drink) is
begin
D.Available := Maximum_Capacity;
end Refill;
-- Profit --
function Profit(D : Drink) return Float is
begin
return Float(Integer(D.Money_Made * 100.0)) / 100.0;
end Profit;
-- Drinks_Sold --
function Drinks_Sold(D : Drink) return Integer is
begin
return D.Consumed;
end Drinks_Sold;
-- Get_Drink_Name --
function Get_Drink_Name(D : Drink) return String is
begin
return To_String(D.Name);
end Get_Drink_Name;
-- Restock_Amount --
function Restock_Amount(D : Drink) return Integer is
begin
return Maximum_Capacity - D.Available;
end Restock_Amount;
end Drink_Package;
machine.adb - The Main Program
-- machine.adb
-- Main driver program for the Vending Machine
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Drink_Package; use Drink_Package;
procedure Machine is
Drink1, Drink2, Drink3, Drink4 : Drink;
Total_Sold : Integer;
Total_Profit : Float;
begin
-- Create drinks using different constructors
Drink1 := Create("Coke");
Drink2 := Create("Sprite");
Drink3 := Create("Doctor Pepper");
Drink4 := Create("V8 Splash", 4.25);
-- Refill all drinks
Refill(Drink1);
Refill(Drink2);
Refill(Drink3);
Refill(Drink4);
-- Purchase 8 Cokes
for I in 1 .. 8 loop
Vend(Drink1);
end loop;
-- Purchase 6 Sprites
for I in 1 .. 6 loop
Vend(Drink2);
end loop;
-- Purchase 2 Doctor Peppers
Vend(Drink3);
Vend(Drink3);
-- Purchase 11 V8 Splash (will exceed capacity)
for I in 1 .. 11 loop
Vend(Drink4);
end loop;
-- Calculate totals
Total_Sold := Drinks_Sold(Drink1) + Drinks_Sold(Drink2) +
Drinks_Sold(Drink3) + Drinks_Sold(Drink4);
Total_Profit := Profit(Drink1) + Profit(Drink2) +
Profit(Drink3) + Profit(Drink4);
-- Display results
Put("A Total of ");
Put(Total_Sold, Width => 0);
Put(" drinks were sold for a profit of $");
Put(Total_Profit, Fore => 1, Aft => 2, Exp => 0);
New_Line;
New_Line;
Put(Restock_Amount(Drink1), Width => 0);
Put_Line(" " & Get_Drink_Name(Drink1) & " needed to restock machine");
Put(Restock_Amount(Drink2), Width => 0);
Put_Line(" " & Get_Drink_Name(Drink2) & " needed to restock machine");
Put(Restock_Amount(Drink3), Width => 0);
Put_Line(" " & Get_Drink_Name(Drink3) & " needed to restock machine");
Put(Restock_Amount(Drink4), Width => 0);
Put_Line(" " & Get_Drink_Name(Drink4) & " needed to restock machine");
end Machine;
⚡ Compilation and Execution Instructions
Step-by-Step: Compilation with GNAT (Correct Method)
- Create
drink_package.adswith the package specification (shown above) - Create
drink_package.adbwith the package body (shown above) - Create
machine.adbwith the main procedure (shown above) - Save all three files in the same directory
- Open a terminal and navigate to that directory
- Compile the program using gnatmake:
gnatmake machine.adb - Run the program:
./machine
✅ The Key to Success
Use with Drink_Package; in machine.adb. The GNAT compiler will automatically locate and compile drink_package.ads and drink_package.adb when it processes the with clause.
The Difference Between File Types in Ada
| File Type | Extension | Purpose | Contains |
|---|---|---|---|
| Package Specification | .ads |
Public interface declaration | Type declarations, subprogram specs, constants |
| Package Body | .adb |
Implementation of the specification | Subprogram bodies, private code |
| Main Procedure | .adb |
Executable entry point | procedure Name is ... begin ... end Name; |
Sample Output:
🎯 Understanding the Code
Package Structure and the with/use Clauses
In Ada, when you split code into multiple files, you use packages with specifications and bodies:
| File | Section | Purpose |
|---|---|---|
drink_package.ads |
package Drink_Package is |
Declares the public interface |
drink_package.ads |
private |
Hidden implementation details |
drink_package.adb |
package body Drink_Package is |
Implements all subprograms |
machine.adb |
with Drink_Package; |
Makes package visible |
machine.adb |
use Drink_Package; |
Allows direct name access (no prefix needed) |
Why Separate Specification and Body?
The separation is essential because:
- Compilation Efficiency: Other units only need to read the specification (
.ads) - Information Hiding: Implementation details in the body are hidden from clients
- Clear API: The specification serves as documentation of what the package provides
- Contract-Based Design: Specifications define contracts that bodies must fulfill
Tagged Types and Object-Oriented Programming
| Aspect | Ada Approach | Description |
|---|---|---|
| Class Definition | type Drink is tagged record |
Tagged types enable OOP features |
| Encapsulation | tagged private |
Full record hidden in private section |
| Methods | Subprograms with type as parameter | procedure Vend(D : in out Drink) |
| Inheritance | type Child is new Parent with record |
Extends parent with new fields |
Parameter Modes in Ada
Ada uses explicit parameter modes to indicate how parameters are used:
| Mode | Meaning | Example |
|---|---|---|
in |
Read-only (default for functions) | function Profit(D : Drink) |
out |
Write-only, value assigned inside | procedure Get_Value(Result : out Integer) |
in out |
Read and write (like pass by reference) | procedure Vend(D : in out Drink) |
Encapsulation in Action
Notice that in the Drink_Package specification, the type Drink is declared as tagged private. This means the actual record structure cannot be accessed directly from outside the package. Instead, users must use the public subprograms:
Vend(D)- handles selling a drinkRefill(D)- restocks the drinksProfit(D)- calculates earningsDrinks_Sold(D)- returns sales count
✅ This is encapsulation
The internal details (how money is tracked, how inventory works) are hidden in the private section, and users interact through well-defined subprograms.
❌ Common Mistakes and Solutions
Mistake 1: Forgetting the with Clause
-- Missing: with Drink_Package;
procedure Machine is
D : Drink; -- ERROR: Drink is not visible
begin
D := Create("Coke");
end Machine;
with Drink_Package; use Drink_Package;
procedure Machine is
D : Drink; -- Now Drink is visible
begin
D := Create("Coke");
end Machine;
Mistake 2: Using Wrong Assignment Operator
D.Name = To_Unbounded_String(Brand_Name); -- ERROR: = is comparison
D.Name := To_Unbounded_String(Brand_Name); -- Ada uses := for assignment
Mistake 3: Forgetting in Out for Modifying Parameters
procedure Vend(D : Drink) is -- ERROR: D is read-only by default
begin
D.Available := D.Available - 1; -- Cannot modify D!
end Vend;
procedure Vend(D : in Out Drink) is -- D can be read and modified
begin
D.Available := D.Available - 1; -- Now this works
end Vend;
Mistake 4: Using + for String Concatenation
Put_Line(To_String(D.Name) + " Purchased."); -- ERROR: + doesn't concatenate strings
Put_Line(To_String(D.Name) & " Purchased."); -- Ada uses & for concatenation
Mistake 5: Package Name Not Matching File Name
package Beverage is -- ERROR! Package name should match filename
...
end Beverage;
package Drink_Package is -- Package name matches filename
...
end Drink_Package;
Mistake 6: Forgetting end Statements
if D.Available > 0 then
D.Available := D.Available - 1;
-- ERROR: Missing "end if;"
if D.Available > 0 then
D.Available := D.Available - 1;
end if; -- Ada requires explicit end statements
Mistake 7: Using return in a Procedure
procedure Vend(D : in out Drink) is
begin
if D.Available = 0 then
return D; -- ERROR: Procedures don't return values
end if;
end Vend;
procedure Vend(D : in out Drink) is
begin
if D.Available = 0 then
return; -- Procedures use bare "return" to exit early
end if;
end Vend;
Mistake 8: Forgetting to Import Text_IO Packages
procedure Machine is
begin
Put_Line("Hello"); -- ERROR: Put_Line is not visible
end Machine;
with Ada.Text_IO; use Ada.Text_IO;
procedure Machine is
begin
Put_Line("Hello"); -- Now Put_Line is visible
end Machine;
✏️ Your Programming Exercise
Objective:
Modify the vending machine program to replace the V8 Splash drink with a new beverage of your choice.
Assignment:
The current program includes V8 Splash at $4.25 as the fourth drink. Your task is to:
- Replace V8 Splash: Change the drink name to something different (e.g., "Orange Juice", "Apple Cider", "Iced Tea", "Lemonade", etc.) and choose a new price (different from $4.25)
- Modify the vending operations: Change how many times each drink is purchased by modifying the loop counts for each drink's Vend calls
- Update the output display: Ensure all the drink names and restock amounts are printed correctly
- Test your program: Compile with
gnatmakeand run it to ensure it works properly
Specific Changes to Make:
- In
machine.adb, change the line:
to your new drink with a new price, for example:Drink4 := Create("V8 Splash", 4.25);Drink4 := Create("Orange Juice", 3.50); - Keep all four drinks in the program
- Keep all the Vend calls (you can change the loop ranges)
- Keep the final output section that displays totals and restocking information
Testing Your Solution:
- Make your changes to
machine.adb - Save the file
- Compile:
gnatmake machine.adb - Run:
./machine - Verify the output shows your new drink name and the correct profit calculations
🎯 Challenge:
Before running your program, calculate what the total profit should be. Does your output match your prediction?
⭐ Key Takeaways About Object-Oriented Programming
- Classes bundle data (variables) and behavior (methods) together
- Constructors initialize objects when they are created
- Access modifiers (
privatesections) control what can be accessed - Encapsulation hides internal details and prevents misuse
- Methods provide the interface for interacting with objects
- Ada uses
tagged recordtypes for OOP instead of classes - Ada uses
function Create return Tpattern for constructors - Use
with Package_Name;to import packages - Use
use Package_Name;for direct visibility of names - Ada requires explicit parameter modes:
in,out,in out - Package names should match file names (
drink_package.adscontainspackage Drink_Package) - String concatenation in Ada uses the
&operator - Ada uses
Put_Line()for output (fromAda.Text_IO) - Use
gnatmaketo compile Ada programs - All compound statements require explicit
end(e.g.,end if;,end loop;)
📚 Language Strengths Summary
| Language | Strengths | Best For |
|---|---|---|
| Java | Write once, run anywhere; Strong ecosystem; Great for large projects | Enterprise applications, web backends, Android apps |
| C++ | High performance; Maximum control; Close to hardware | System software, games, performance-critical apps |
| Ada | Strong typing; Safety-critical features; Excellent readability; Built-in concurrency | Aerospace, defense, safety-critical systems, embedded systems |