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: - TSalariedEmployeeCalcPay 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:

  1. A TSerializable abstract base class with virtual methods: - SaveToStream(Stream: TStream) — abstract - LoadFromStream(Stream: TStream) — abstract - SaveToFile(FileName: string) — non-virtual, calls SaveToStream - LoadFromFile(FileName: string) — non-virtual, calls LoadFromStream

  2. 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)

  3. A TGameWorld class that: - Maintains a list of TSerializable entities - 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 loaded TPlayer is truly a TPlayer, not just a TGameEntity)

  4. 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:

  1. Create a TReportGenerator abstract base class with: - A virtual constructor - An abstract Generate(Expenses: array of TExpense): string method - An abstract FormatName: string method

  2. Create three concrete generators: - TPlainTextReport — generates a simple text table - TCSVReport — generates comma-separated values - TSummaryReport — generates a summary by category with totals

  3. Create a TReportRegistry class 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

  4. Demonstrate that new report formats can be added without modifying existing code — the Open/Closed Principle in action.