Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphism and inheritance using class reference?

The output of the console application below is

Parent
Parent
Parent

instead of

Parent
Child1
Child2

Why would this happen ? Furthermore, how to get the intended output ? Many thanks !

PS: Still no clue after reading this related SO post ...

program Project1;

{$APPTYPE CONSOLE}

type
  TParent = class;
  TParentClass = class of TParent;

  TParent = class
  public
    ID: string;
    constructor Create;
  end;

  TChild1 = class(TParent)
  public
    constructor Create;
  end;  

  TChild2 = class(TParent)
  public
    constructor Create;
  end;

constructor TParent.Create;
begin
  ID := 'Parent';
end;

constructor TChild1.Create;
begin
  ID := 'Child1';
end;    

constructor TChild2.Create;
begin
  ID := 'Child2';
end;

procedure Test(ImplClass: TParentClass);
var
  ImplInstance: TParent;
begin
  ImplInstance := ImplClass.Create;
  WriteLn(ImplInstance.ID);
  ImplInstance.Free;
end;

begin
  Test(TParent);
  Test(TChild1);
  Test(TChild2);
  Readln;
end.
like image 559
SOUser Avatar asked May 13 '14 09:05

SOUser


1 Answers

Your code behaves the way it does because your constructors are not virtual. Which means that the compiler binds to them at compile time. Which therefore means that the runtime type cannot be taken into account and the code always calls TParent.Create.

In order to allow the program to bind using the runtime type, you need to use virtual methods and polymorphism. So you can solve your problem by using virtual constructors:

program Project1;

{$APPTYPE CONSOLE}

type
  TParent = class;
  TParentClass = class of TParent;

  TParent = class
  public
    ID: string;
    constructor Create; virtual;
  end;

  TChild1 = class(TParent)
  public
    constructor Create; override;
  end;

  TChild2 = class(TParent)
  public
    constructor Create; override;
  end;

constructor TParent.Create;
begin
  ID := 'Parent';
end;

constructor TChild1.Create;
begin
  ID := 'Child1';
end;

constructor TChild2.Create;
begin
  ID := 'Child2';
end;

procedure Test(ImplClass: TParentClass);
var
  ImplInstance: TParent;
begin
  ImplInstance := ImplClass.Create;
  WriteLn(ImplInstance.ID);
  ImplInstance.Free;
end;

begin
  Test(TParent);
  Test(TChild1);
  Test(TChild2);
  Readln;
end.

Output

Parent
Child1
Child2

A rule of thumb here is that whenever you use a meta-class to instantiate an object, your classes constructor should be virtual. This rule of thumb has exceptions, but I personally have never had to break this rule in my production code.

like image 57
David Heffernan Avatar answered Nov 15 '22 04:11

David Heffernan