Polymorphism and inheritance using class reference

2019-06-21 14:11发布

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.

1条回答
2楼-- · 2019-06-21 14:54

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.

查看更多
登录 后发表回答