Welcome to a comprehensive comparison of Object-Oriented Programming (OOP) across three distinct programming languages: Java, C++, and Pascal. This document explores how each language approaches the fundamental concepts of OOP and provides practical examples using the Free Pascal Compiler (fpc). 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.
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:
| Feature | Java | C++ | Pascal |
|---|---|---|---|
| Constructor | ClassName() |
ClassName() |
constructor Create() |
| Destructor | Automatic (Garbage Collected) | ~ClassName() |
destructor Destroy() |
| Inheritance | extends |
: |
class(ParentClass) |
| Access Modifiers | public, private, protected |
public, private, protected |
public, private, protected, published |
| Method Override | @Override annotation |
virtual keyword |
virtual/override keywords |
| Polymorphism | Virtual by default | Must use virtual |
Must use virtual |
| Memory Management | Automatic (Garbage Collection) | Manual (new/delete) |
Manual (Create/Free) |
| Interfaces | interface keyword |
Pure virtual classes | interface keyword |
| Constants | final keyword |
const keyword |
const keyword |
| Compilation | javac |
g++ |
fpc |
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;
}
};
type
TDrink = class
private
const MAXIMUM = 10;
FName: string;
public
constructor Create(brandName: string);
function GetName(): string;
end;
constructor TDrink.Create(brandName: string);
begin
FName := brandName;
end;
function TDrink.GetName(): string;
begin
Result := FName;
end;
final for constantsprivate: and public: sections; uses static const for class constantstype block; implements methods separately outside the class; uses T prefix convention for type names and F prefix for private fieldsAll 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;
}
constructor TDrink.Create(brandName: string);
begin
FName := brandName;
FCost := 1.75;
end;
constructor TDrink.Create(brandName: string; cost: Double);
begin
FName := brandName;
FCost := cost;
end;
this.variable to reference instance variablesthis->variable pointer notationSelf.variable (optional when unambiguous); uses := for assignment; parameters separated by semicolonsHere'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 TDrink.Vend();
begin
if FAvailable > 0 then
begin
FAvailable := FAvailable - 1;
FConsumed := FConsumed + 1;
FMoneyMade := FMoneyMade + FCost;
WriteLn(FName + ' Purchased.');
end;
end;
function TDrink.Profit(): Double;
begin
Result := FMoneyMade;
end;
System.out.println() for outputcout and endl from iostreamWriteLn() for output; uses + for string concatenation; uses procedure for methods with no return value and function for methods that return values; uses Result to specify return valueNow let's look at a complete, working example using the Pascal language and the Free Pascal Compiler (fpc). This program implements a simple vending machine that tracks drink sales and profits using separate unit and program files.
uses Clause in PascalWhen using Pascal with separate files, you organize code into units. Each unit has an interface section (public declarations) and an implementation section (actual code). Other files include units with the uses clause.
DrinkUnit.pas) with the class definition and a program file (Machine.pas) that uses that unit.
The unit system in Pascal is crucial because:
interface section declares what's public; implementation contains the actual codeuses clause explicitly shows what modules your code depends onuses DrinkUnit;, the compiler looks for DrinkUnit.pas (or a pre-compiled DrinkUnit.ppu), reads its interface section, and makes those declarations available to your program.
unit DrinkUnit;
{$mode objfpc}{$H+}
interface
type
TDrink = class
private
const MAXIMUM_CAPACITY = 10;
const DEFAULT_DRINK_COST = 1.75;
private
FAvailable: Integer;
FConsumed: Integer;
FUseDefaultCost: Integer;
FUseSuppliedCost: Integer;
FDrinkCost: Double;
FMoneyMade: Double;
FName: string;
public
constructor Create(brandName: string);
constructor Create(brandName: string; cost: Double);
procedure Vend();
procedure Refill();
function Profit(): Double;
function DrinksSold(): Integer;
function GetDrinkName(): string;
function RestockAmount(): Integer;
end;
implementation
uses
Math;
constructor TDrink.Create(brandName: string);
begin
FName := brandName;
FConsumed := 0;
FAvailable := 0;
FMoneyMade := 0.0;
FDrinkCost := DEFAULT_DRINK_COST;
FUseDefaultCost := 1;
FUseSuppliedCost := 0;
end;
constructor TDrink.Create(brandName: string; cost: Double);
begin
FName := brandName;
FConsumed := 0;
FAvailable := 0;
FMoneyMade := 0.0;
FDrinkCost := cost;
FUseDefaultCost := 0;
FUseSuppliedCost := 1;
end;
procedure TDrink.Vend();
begin
if FAvailable > 0 then
begin
FAvailable := FAvailable - 1;
FConsumed := FConsumed + 1;
FMoneyMade := FMoneyMade + (FUseDefaultCost * DEFAULT_DRINK_COST +
FUseSuppliedCost * FDrinkCost);
WriteLn(FName + ' Purchased.');
end
else
begin
WriteLn('');
WriteLn(' SOLD OUT ');
WriteLn('');
end;
end;
procedure TDrink.Refill();
begin
FAvailable := MAXIMUM_CAPACITY;
end;
function TDrink.Profit(): Double;
begin
Result := Round(FMoneyMade * 100.0) / 100.0;
end;
function TDrink.DrinksSold(): Integer;
begin
Result := FConsumed;
end;
function TDrink.GetDrinkName(): string;
begin
Result := FName;
end;
function TDrink.RestockAmount(): Integer;
begin
Result := MAXIMUM_CAPACITY - FAvailable;
end;
end.
program Machine;
{$mode objfpc}{$H+}
uses
DrinkUnit;
var
drink1, drink2, drink3, drink4: TDrink;
totalSold: Integer;
totalProfit: Double;
begin
// Create drinks using different constructors
drink1 := TDrink.Create('Coke');
drink2 := TDrink.Create('Sprite');
drink3 := TDrink.Create('Doctor Pepper');
drink4 := TDrink.Create('V8 Splash', 4.25);
// Refill all drinks
drink1.Refill();
drink2.Refill();
drink3.Refill();
drink4.Refill();
// Purchase 8 Cokes
drink1.Vend();
drink1.Vend();
drink1.Vend();
drink1.Vend();
drink1.Vend();
drink1.Vend();
drink1.Vend();
drink1.Vend();
// Purchase 6 Sprites
drink2.Vend();
drink2.Vend();
drink2.Vend();
drink2.Vend();
drink2.Vend();
drink2.Vend();
// Purchase 2 Doctor Peppers
drink3.Vend();
drink3.Vend();
// Purchase 11 V8 Splash (will exceed capacity)
drink4.Vend();
drink4.Vend();
drink4.Vend();
drink4.Vend();
drink4.Vend();
drink4.Vend();
drink4.Vend();
drink4.Vend();
drink4.Vend();
drink4.Vend();
drink4.Vend();
// Calculate totals
totalSold := drink1.DrinksSold() + drink2.DrinksSold() +
drink3.DrinksSold() + drink4.DrinksSold();
totalProfit := drink1.Profit() + drink2.Profit() +
drink3.Profit() + drink4.Profit();
// Display results
WriteLn('A Total of ', totalSold, ' drinks were sold for a profit of ', totalProfit:0:2);
WriteLn();
WriteLn(drink1.RestockAmount(), ' ', drink1.GetDrinkName(), ' needed to restock machine');
WriteLn(drink2.RestockAmount(), ' ', drink2.GetDrinkName(), ' needed to restock machine');
WriteLn(drink3.RestockAmount(), ' ', drink3.GetDrinkName(), ' needed to restock machine');
WriteLn(drink4.RestockAmount(), ' ', drink4.GetDrinkName(), ' needed to restock machine');
// Free memory
drink1.Free;
drink2.Free;
drink3.Free;
drink4.Free;
end.
DrinkUnit.pas with the TDrink class code (shown above)Machine.pas with the main program code (shown above)fpc Machine.pas
./Machine
uses DrinkUnit; in Machine.pas. The Free Pascal compiler will automatically locate and compile DrinkUnit.pas when it processes the uses clause.
| File Type | Declaration | Purpose | Contains |
|---|---|---|---|
| Unit | unit UnitName; |
Reusable module with classes/functions | interface and implementation sections |
| Program | program ProgramName; |
Executable entry point | begin...end. block with main code |
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Doctor Pepper Purchased.
Doctor Pepper Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
SOLD OUT
A Total of 26 drinks were sold for a profit of $67.75
8 Coke needed to restock machine
4 Sprite needed to restock machine
8 Doctor Pepper needed to restock machine
0 V8 Splash needed to restock machine
uses ClauseIn Pascal, when you split code into multiple files, you use units with interface and implementation sections:
| File | Section | Purpose |
|---|---|---|
| DrinkUnit.pas | unit DrinkUnit; |
Declares that this file is the DrinkUnit module |
| DrinkUnit.pas | interface |
Public declarations visible to other files |
| DrinkUnit.pas | implementation |
Actual method code (private to the unit) |
| Machine.pas | program Machine; |
Declares this as the main executable |
| Machine.pas | uses DrinkUnit; |
Imports the DrinkUnit to access TDrink class |
The separation is essential because:
interface sectioninterface section serves as documentation of what the unit provides| Aspect | Class Constants | Instance Variables |
|---|---|---|
| Declaration | const MAXIMUM_CAPACITY = 10; |
FAvailable: Integer; |
| Scope | Shared by all instances | Unique to each instance |
| Memory | One copy for the whole class | One copy per object |
| Example | All drinks have max capacity of 10 | Each drink tracks its own inventory |
Notice that in the TDrink class, all data members are declared in a private section. This means they cannot be accessed directly from outside the class. Instead, users must use the public methods:
Vend() - handles selling a drinkRefill() - restocks the drinksProfit() - calculates earningsDrinksSold() - returns sales countunit DrinkUnit;
interface
type
TDrink = class // ERROR: Class not recognized without ObjFPC mode
unit DrinkUnit;
{$mode objfpc}{$H+} // Enable Object Pascal mode and long strings
interface
type
TDrink = class // Works correctly
begin
drink1 := TDrink.Create('Coke');
drink1.Vend();
// Program ends without freeing memory - MEMORY LEAK!
end.
begin
drink1 := TDrink.Create('Coke');
drink1.Vend();
drink1.Free; // Always free objects when done
end.
FName = brandName; // ERROR: This is comparison, not assignment
FName := brandName; // Pascal uses := for assignment
begin/end Blocksif FAvailable > 0 then
FAvailable := FAvailable - 1;
FConsumed := FConsumed + 1; // This line always executes!
if FAvailable > 0 then
begin
FAvailable := FAvailable - 1;
FConsumed := FConsumed + 1; // Now properly inside the if block
end;
unit Beverage; // ERROR! Unit name should match filename
unit DrinkUnit; // Unit name matches filename
uses Clauseprogram Machine;
{$mode objfpc}{$H+}
var
drink1: TDrink; // ERROR: TDrink is not defined
begin
drink1 := TDrink.Create('Coke');
end.
program Machine;
{$mode objfpc}{$H+}
uses
DrinkUnit; // Import the unit containing TDrink
var
drink1: TDrink; // Now TDrink is recognized
begin
drink1 := TDrink.Create('Coke');
drink1.Free;
end.
return Instead of Resultfunction TDrink.Profit(): Double;
begin
return FMoneyMade; // ERROR: Pascal doesn't use 'return'
end;
function TDrink.Profit(): Double;
begin
Result := FMoneyMade; // Pascal uses Result for return values
end;
Objective: Modify the vending machine program to replace the V8 Splash drink with a new beverage of your choice.
The current program includes V8 Splash at $4.25 as the fourth drink. Your task is to:
Vend() method calls for each drinkMachine.pas, change the line:
drink4 := TDrink.Create('V8 Splash', 4.25);
to your new drink with a new price, for example:
drink4 := TDrink.Create('Orange Juice', 3.50);
Vend() calls (you can change the numbers of times each is called)Machine.pas (or DrinkUnit.pas if needed)fpc Machine.pas
./Machine
private, public) control what can be accessedconstructor Create() for constructors instead of the class nameconst for constants within classesuses clause to import units: uses DrinkUnit;{$mode objfpc} directive for object-oriented featuresDrinkUnit.pas contains unit DrinkUnit)+ operatorWriteLn() for output.Free on objects when done to prevent memory leaksResult to specify return values in functions| 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 |
| Pascal | Clear readable syntax; Strong typing; Excellent for learning | Education, rapid application development, cross-platform GUI apps |