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