4

My top level class is TBaseDB, which has a descendant TCommonDB (, and TCommonDB will have multiple descendants, like TProdDB and TDevDB).

Let's create a function in each class definition, called Test1. For now, all it does is a ShowMessage('Some literal') just to show me what code is being executed.

I don't know the class type until runtime. I want to have common code, but different behavior.

What I want is something like this:

var
  MyObj: TBaseDB;
begin        
  //pseudo-code...
  if RadioButton1.Checked then
    MyObj := TBaseDB.Create
  else
    MyObj := TCommonDB.create;
  MyObj.Test1;    
end;

I can't seem to get this to work, and I imagine it is in my class definition. How should Test1 be defined so that:

  1. I can declare my variable as TBaseDB,
  2. the created class can be either TBaseDB or TCommonDB, and
  3. the proper Test procedure will be called depending on the instance being a TBaseDB or TCommonDB?
NGLN
  • 43,011
  • 8
  • 105
  • 200
user1009073
  • 3,160
  • 7
  • 40
  • 82

1 Answers1

12
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TFruit = class
  public
    procedure ShowMessage; virtual; abstract;
  end;

  TApple = class(TFruit)
  public
    procedure ShowMessage; override;
  end;

  TOrange = class(TFruit)
  public
    procedure ShowMessage; override;
  end;


{ TApple }

procedure TApple.ShowMessage;
begin
  Writeln('I''m an apple!');
end;

{ TOrange }

procedure TOrange.ShowMessage;
begin
  Writeln('I''m an orange!');
end;

var
  fruit: TFruit;

begin

  fruit := TApple.Create;

  fruit.ShowMessage;

  Writeln('Press Enter to continue.');
  Readln;

end.

The keyword abstract allows you to have no implementation at all in the base class. You can also, however, have an implementation there as well:

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TFruit = class
  public
    procedure ShowMessage; virtual;
  end;

  TApple = class(TFruit)
  public
    procedure ShowMessage; override;
  end;

  TOrange = class(TFruit)
  public
    procedure ShowMessage; override;
  end;


{ TFruit }

procedure TFruit.ShowMessage;
begin
  Writeln('I''m a fruit.');
end;

{ TApple }

procedure TApple.ShowMessage;
begin
  inherited;
  Writeln('I''m an apple!');
end;

{ TOrange }

procedure TOrange.ShowMessage;
begin
  inherited;
  Writeln('I''m an orange!');
end;

var
  fruit: TFruit;

begin

  fruit := TApple.Create;

  fruit.ShowMessage;

  Writeln('Press Enter to continue.');
  Readln;

end.

Exercises:

  1. In each case, what happens if you create an instance of TFruit?
  2. In the second case, what does inherited in TApple.ShowMessage and TOrange.ShowMessage mean? Do they need to be at the top of the procedures? What happens if you omit them?
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • The combination of virtual in the ancestor, and override in the descendant was what I was missing. That did what I wanted. As an FYI, you state that ABSTRACT can have an implementation. When I tried this, I get the error 'No definition for abstract method 'Test1' allowed. This may be specific to Delphi 2010... Thanks for the great example! – user1009073 Feb 27 '13 at 15:03
  • @user, read it carefully ;-) Andreas wrote, *"the keyword abstract allows you to have no implementation at all"* and then mentioned, that you can have there an implementation (if you omit the `abstract` keyword). – TLama Feb 27 '13 at 15:10
  • Fair enough... I read 'allows' to mean optionality, but really, abstract FORCES no implementation. Is that a more accurate statement? – user1009073 Feb 27 '13 at 15:21
  • @user1009073: Well, it is certainly a correct statement, but I'm not sure I'd say it's 'more accurate' than my original one! :) – Andreas Rejbrand Feb 27 '13 at 15:35