Exercises: Inheritance, Polymorphism, and Virtual Methods
Part A: Conceptual Questions
A1. Explain the "is-a" relationship in your own words. Give two examples from everyday life (not programming) that would make good inheritance hierarchies and one example that looks like inheritance but is actually a "has-a" relationship.
A2. What is the difference between method hiding and method overriding? What keywords does Object Pascal use for each, and why does the distinction matter for polymorphism?
A3. Explain why TObject.Destroy is declared as virtual. What would go wrong if it were not virtual?
A4. A student writes this hierarchy: TBird = class with a virtual method Fly, and then TPenguin = class(TBird) that overrides Fly to raise an exception ("Penguins can't fly!"). Does this violate the Liskov Substitution Principle? Explain your reasoning.
A5. Why does Free Pascal use single inheritance rather than multiple inheritance? What mechanism does it provide to compensate for this limitation?
A6. Explain the difference between virtual and dynamic methods. When might you prefer dynamic over virtual?
A7. What is an abstract method? Why would you declare a method abstract instead of providing a default implementation in the base class?
Part B: Tracing and Prediction
For each code fragment, predict the output without running the code. Then verify by compiling and running.
B1.
type
TBase = class
procedure Greet; virtual;
end;
TChild = class(TBase)
procedure Greet; override;
end;
procedure TBase.Greet;
begin
WriteLn('Hello from Base');
end;
procedure TChild.Greet;
begin
WriteLn('Hello from Child');
end;
var
Obj: TBase;
begin
Obj := TChild.Create;
Obj.Greet;
Obj.Free;
end.
B2.
type
TBase = class
procedure Greet; // NOT virtual
end;
TChild = class(TBase)
procedure Greet;
end;
procedure TBase.Greet;
begin
WriteLn('Hello from Base');
end;
procedure TChild.Greet;
begin
WriteLn('Hello from Child');
end;
var
Obj: TBase;
begin
Obj := TChild.Create;
Obj.Greet;
Obj.Free;
end.
B3.
type
TAnimal = class
procedure Speak; virtual;
end;
TDog = class(TAnimal)
procedure Speak; override;
end;
TGoldenRetriever = class(TDog)
procedure Speak; override;
end;
procedure TAnimal.Speak;
begin
WriteLn('...');
end;
procedure TDog.Speak;
begin
WriteLn('Woof!');
end;
procedure TGoldenRetriever.Speak;
begin
inherited;
WriteLn('*wags tail*');
end;
var
A: TAnimal;
begin
A := TGoldenRetriever.Create;
A.Speak;
A.Free;
end.
B4.
type
TA = class
constructor Create;
destructor Destroy; override;
end;
TB = class(TA)
constructor Create;
destructor Destroy; override;
end;
constructor TA.Create;
begin
inherited Create;
WriteLn('TA.Create');
end;
destructor TA.Destroy;
begin
WriteLn('TA.Destroy');
inherited Destroy;
end;
constructor TB.Create;
begin
inherited Create;
WriteLn('TB.Create');
end;
destructor TB.Destroy;
begin
WriteLn('TB.Destroy');
inherited Destroy;
end;
var
Obj: TA;
begin
Obj := TB.Create;
Obj.Free;
end.
B5.
type
TShape = class
function Area: Double; virtual; abstract;
end;
TCircle = class(TShape)
Radius: Double;
constructor Create(R: Double);
function Area: Double; override;
end;
TSquare = class(TShape)
Side: Double;
constructor Create(S: Double);
function Area: Double; override;
end;
constructor TCircle.Create(R: Double);
begin
inherited Create;
Radius := R;
end;
function TCircle.Area: Double;
begin
Result := Pi * Radius * Radius;
end;
constructor TSquare.Create(S: Double);
begin
inherited Create;
Side := S;
end;
function TSquare.Area: Double;
begin
Result := Side * Side;
end;
var
Shapes: array[0..1] of TShape;
i: Integer;
begin
Shapes[0] := TCircle.Create(5.0);
Shapes[1] := TSquare.Create(4.0);
for i := 0 to 1 do
begin
WriteLn(Shapes[i].ClassName, ': ', Shapes[i].Area:0:2);
Shapes[i].Free;
end;
end.
Part C: Short Programming Exercises
C1. Vehicle Hierarchy
Create a TVehicle base class with fields Make, Model, and Year, and a virtual method Describe. Create two derived classes: TCar (add NumDoors: Integer) and TTruck (add PayloadTons: Double). Each overrides Describe to include its specific information. Create an array of three vehicles (mix of cars and trucks) and iterate through it, calling Describe polymorphically.
C2. Employee Hierarchy
Create a TEmployee base class with Name and BaseSalary fields and a virtual method CalcPay: Double. Create two derived classes:
- TSalariedEmployee — CalcPay returns BaseSalary
- THourlyEmployee — adds HoursWorked and HourlyRate fields; CalcPay returns HoursWorked * HourlyRate
Write a function CalcPayroll that accepts an array of TEmployee and returns the total pay. Test it with a mix of salaried and hourly employees.
C3. Instrument Hierarchy
Create a TInstrument class with a virtual Play method. Derive TPiano, TGuitar, and TDrum, each overriding Play to print a different sound (e.g., "Plink plink", "Strum strum", "Boom boom"). Add a virtual Tune method to TInstrument with a default implementation that prints "Tuning...". Override it in TGuitar to also print "Adjusting string tension." Use inherited to call the base Tune from TGuitar.Tune.
C4. Abstract Shape
Create a TShape class with abstract methods Area: Double and Perimeter: Double. Derive TCircle (with Radius) and TRectangle (with Width and Height). Implement all abstract methods. Write a procedure PrintShapeInfo(Shape: TShape) that prints the class name, area, and perimeter — using only base-class references.
C5. is and as Practice
Given the TGameEntity hierarchy from this chapter (with TPlayer, TMonster, TNPC), write a procedure EntityDetails(Entity: TGameEntity) that:
- Prints the entity's ClassName and basic info for all entities
- If the entity is a TPlayer, additionally prints the level and experience
- If the entity is a TMonster, additionally prints the damage
- If the entity is a TNPC, additionally prints the dialogue
Use both is and as operators. Then explain in a comment why this approach is sometimes unavoidable despite being a "design smell."
C6. Constructor/Destructor Chain
Create a three-level hierarchy: TBase -> TMiddle -> TDerived. Each level has a constructor that prints "Creating [class name]" and a destructor that prints "Destroying [class name]". Each must call inherited. Create a TDerived object through a TBase variable, then free it. Verify the output shows creation going base-to-derived and destruction going derived-to-base.
Part D: Longer Programming Challenges
D1. Zoo Simulation Design a zoo simulation with the following hierarchy:
TAnimal
├── TMammal
│ ├── TLion
│ └── TElephant
├── TBird
│ ├── TEagle
│ └── TPenguin
└── TReptile
├── TCrocodile
└── TTurtle
Each animal has a Name, Age, FeedingTime, and virtual methods Speak: string, Move: string, and Describe. Each concrete class overrides all three. Write a TZoo class that maintains a list of TAnimal objects and provides:
- AddAnimal(Animal: TAnimal)
- FeedAll — iterates and prints feeding info
- RollCall — iterates and calls Describe on each
- CountByType(AClass: TClass): Integer — counts animals of a specific type using is
Test with at least six animals (one of each concrete type).
D2. Expression Tree Create an expression tree using inheritance:
TExpression (abstract)
├── TNumberExpr (holds a Double value)
├── TBinaryExpr (abstract, holds Left and Right TExpression)
│ ├── TAddExpr
│ ├── TSubtractExpr
│ ├── TMultiplyExpr
│ └── TDivideExpr
└── TNegateExpr (holds one TExpression)
Each class overrides a virtual Evaluate: Double method. Build the expression (3 + 4) * -(2 - 1) as a tree and evaluate it (should produce -7). Also override a virtual ToString: string method that produces the parenthesized expression string.
D3. Banking System Create a banking account hierarchy:
TAccount
├── TCheckingAccount (overdraft limit)
├── TSavingsAccount (interest rate, minimum balance)
└── TCreditAccount (credit limit, APR)
TAccount has virtual methods Deposit, Withdraw (returns Boolean), and CalcMonthlyFee: Double. Each derived class overrides Withdraw with its own rules (checking allows overdraft up to limit; savings requires minimum balance; credit allows spending up to credit limit). Each overrides CalcMonthlyFee differently. Write a TBank class that holds an array of accounts and provides ApplyMonthlyFees and PrintStatements — both using polymorphism.
Part E: Analysis and Design
E1. LSP Analysis For each of the following proposed hierarchies, determine whether the Liskov Substitution Principle is satisfied. Explain your reasoning.
a) TReadOnlyList = class(TList) — where TList has Add and Remove methods and TReadOnlyList overrides them to raise exceptions.
b) TColoredCircle = class(TCircle) — where TCircle has Radius and Center, and TColoredCircle adds Color.
c) TStack = class(TDynamicArray) — where TDynamicArray provides indexed access and TStack overrides the indexer to raise an exception.
d) TElectricCar = class(TCar) — where TCar has Refuel(Gallons: Double) and TElectricCar overrides it to charge a battery instead.
E2. Refactoring Challenge
The following code uses a case statement to handle different entity types. Refactor it to use polymorphism (virtual methods) instead. Show both the class hierarchy and the calling code.
procedure ProcessEntity(EntityType: Integer; Name: string; HP: Integer);
begin
case EntityType of
1: begin // Player
WriteLn('Player ', Name, ' (HP: ', HP, ')');
WriteLn(' Waiting for input...');
end;
2: begin // Monster
WriteLn('Monster ', Name, ' (HP: ', HP, ')');
WriteLn(' Attacking nearest player...');
end;
3: begin // NPC
WriteLn('NPC ', Name, ' (HP: ', HP, ')');
WriteLn(' Standing idle...');
end;
end;
end;
E3. Hierarchy Design
You are building a document editor that handles different content types: paragraphs, headings, images, tables, and code blocks. Design a class hierarchy (just the type declarations — no implementations needed). Identify:
- Which methods should be virtual
- Which methods should be virtual; abstract
- Which methods should be non-virtual
- Which relationships are "is-a" vs. "has-a"
Justify each decision in comments.
E4. Composition vs. Inheritance For each scenario, decide whether inheritance or composition is more appropriate. Explain your reasoning.
a) A TLoggedExpense that wraps any TExpense and writes a log entry whenever the amount is accessed.
b) A TRecurringExpense that is an expense with a frequency.
c) A TExpenseWithAttachment that associates a file path with an expense.
d) A TTaxableExpense that computes tax on top of the base amount.
Part M: Mastery Challenge
M1. Polymorphic Serialization System
Design and implement a complete serialization framework using inheritance and polymorphism. Create:
-
A
TSerializableabstract base class with virtual methods: -SaveToStream(Stream: TStream)— abstract -LoadFromStream(Stream: TStream)— abstract -SaveToFile(FileName: string)— non-virtual, callsSaveToStream-LoadFromFile(FileName: string)— non-virtual, callsLoadFromStream -
A hierarchy of game objects that extend
TSerializable: -TGameEntity(Name, X, Y, HP) -TPlayer(Level, Experience, Inventory as array of strings) -TMonster(Damage, MonsterType as string) -TNPC(Dialogue array, QuestID) -
A
TGameWorldclass that: - Maintains a list ofTSerializableentities - Saves and loads the entire world to/from a file - Uses a simple format: write the ClassName, then let each object serialize itself - On loading, uses a factory function to create the correct type based on ClassName - Properly handles the polymorphic nature (a loadedTPlayeris truly aTPlayer, not just aTGameEntity) -
A test program that creates a world with mixed entities, saves it, loads it back, and verifies that all objects round-trip correctly with correct types.
This exercise combines inheritance, polymorphism, virtual methods, is/as operators, constructors/destructors, and file I/O from Chapter 13.
M2. Plugin Architecture
Design a simple plugin system for PennyWise reporting using class references and virtual constructors:
-
Create a
TReportGeneratorabstract base class with: - A virtual constructor - An abstractGenerate(Expenses: array of TExpense): stringmethod - An abstractFormatName: stringmethod -
Create three concrete generators: -
TPlainTextReport— generates a simple text table -TCSVReport— generates comma-separated values -TSummaryReport— generates a summary by category with totals -
Create a
TReportRegistryclass that: - Stores class references (class of TReportGenerator) in a list -Register(GeneratorClass: TReportGeneratorClass)— adds a generator -ListFormats: TStringArray— returns available format names -CreateGenerator(FormatName: string): TReportGenerator— creates the right generator using virtual constructor dispatch -
Demonstrate that new report formats can be added without modifying existing code — the Open/Closed Principle in action.