Inheritance & Polymorphism
26.1 Single inheritance
program Shapes;
{$mode objfpc}{$H+}
uses SysUtils;
type
TShape = class
private
FName: string;
public
constructor Create(const AName: string);
function Area: Double; virtual; abstract; // must be overridden
function Describe: string; virtual;
property Name: string read FName;
end;
TCircle = class(TShape)
private
FRadius: Double;
public
constructor Create(ARadius: Double);
function Area: Double; override;
end;
TRectangle = class(TShape)
private
FW, FH: Double;
public
constructor Create(AW, AH: Double);
function Area: Double; override;
function Describe: string; override;
end;
TSquare = class(TRectangle)
public
constructor Create(ASide: Double);
end;
constructor TShape.Create(const AName: string);
begin
inherited Create;
FName := AName;
end;
function TShape.Describe: string;
begin
Result := Format('%s with area %.2f', [FName, Area]); // Area is dispatched dynamically
end;
constructor TCircle.Create(ARadius: Double);
begin
inherited Create('Circle');
FRadius := ARadius;
end;
function TCircle.Area: Double;
begin
Result := Pi * FRadius * FRadius;
end;
constructor TRectangle.Create
…Circle with area 3.14
Rectangle with area 6.00 (2.0 x 3.0)
Square with area 16.00 (4.0 x 4.0)26.2 Method binding directives
*(none)* — static resolved at compile time from the declared type of the variable dynamic dispatch via the VMT (fast, one table entry per class) dynamic dispatch via a compact table (smaller, slower; for rarely overridden methods) replaces an inherited virtual/dynamic method (same signature) no implementation; descendants must override (calling it raises EAbstractError) hides an inherited virtual method with a new one, suppressing the warning adds an overload alongside inherited methods method cannot be overridden further (override; final;) message handler (procedure WMSize(var Msg: TWMSize); message WM_SIZE;) sealed (class) class cannot be inherited: TFoo = class sealed(TBase) abstract (class)
TFoo = class abstract — documents intent (FPC/Delphi do not forbid instantiation strictly, but warn)
26.3 Static vs virtual: the classic pitfall
type
TAnimal = class
function Speak: string; // static!
function Kind: string; virtual;
end;
TDog = class(TAnimal)
function Speak: string; // hides, does not override
function Kind: string; override;
end;
function TAnimal.Speak: string; begin Result := '...'; end;
function TAnimal.Kind: string; begin Result := 'animal'; end;
function TDog.Speak: string; begin Result := 'Woof'; end;
function TDog.Kind: string; begin Result := 'dog'; end;
var
A: TAnimal;
begin
A := TDog.Create;
Writeln(A.Speak); // '...' — static binding uses the declared type TAnimal
Writeln(A.Kind); // 'dog' — virtual binding uses the actual type
A.Free;
end.26.4 Calling inherited
inherited; calls the ancestor method with the same name and the same arguments.
inherited Name(Args); calls a specific ancestor method.
In constructors call inherited Create(...) first; in destructors call inherited Destroy last.
26.5 Design guidelines
Prefer composition over deep inheritance trees.
Make the base class destructor virtual (TObject.Destroy already is — always override).
Use abstract base classes or interfaces to define contracts.
Do not call virtual methods from constructors expecting descendant fields to be ready — fields are zeroed, but the descendant's constructor body has not run yet (Delphi/FPC do dispatch virtually during construction, unlike C++).