views:

149

answers:

3

Okay, so I have this class, let's say CMain, that contains a CFruit class. What I would like to do is run functions based on CFruit's type (if it's CPear or CApple, etc). So I'd like to do something like this:

type CMain = class
   myFruit : CFruit;
   function GetFruit() : CFruit;
   procedure SetFruit( Fruit : CFruit ); 
end;

procedure CMain.SetFruit( Fruit : CFruit );
begin
  if Fruit.IsPear then .. else etc;
end;

...obviously the compiler stops me from doing this because CFruit is just CPear and CApple's parent. Is there any viable way I can do this? (making sepparate CMain's is out of the question). Thanks.

+7  A: 

IIUC you want virtual methods.

Ulrich Gerhardt
+1. This is the right answer. Virtual methods use the class's type to call whichever version of the method you have defined for that class.
Mason Wheeler
Exactly! So we don't tell him that there is a construct like "if Fruit is CPear then", do we?
Uwe Raabe
**I** won't. ;-)
Ulrich Gerhardt
@Uwe, don't lead him do the dark side! ;-)
splash
@splash: That would never come to my mind...
Uwe Raabe
+3  A: 

Actually there is an "is" operator, that will check if the Object is an instance of class or it's ancestors. This is called "dynamic type checking" and is sorta advanced. Check the help for a clarification.

Depending on your needs "virtual methods" could be what you need as explained by others. Please check the link posted about "virtual methods" as the correct OOP way.

In the example below

if AFruit is TApple then

and

if AFruit is TFruit then

both return true

type
  TFruit = class
  protected
    FName: string;
  public
    property Name: string read FName;
  end;

  TApple = class(TFruit)
  public
    constructor Create;
  end;

  TPear = class(TFruit)
  public
    constructor Create;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    mixed: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    FMixValue: string;
    procedure MixFruits(AFruit: TFruit);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  APear: TPear;
  AApple : TApple;
begin
  APear := TPear.Create;
  AApple := TApple.Create;
  MixFruits(APear);
  MixFruits(AApple);
  mixed.Caption := FMixValue;
end;

{ TPear }

constructor TPear.Create;
begin
  inherited;
  FName := 'Pear';
end;

{ TApple }

constructor TApple.Create;
begin
  inherited;
  FName := 'Apple';
end;

procedure TForm1.MixFruits(AFruit: TFruit);
begin
  FMixValue := FMixValue + ' ' + AFruit.Name;
  if AFruit is TApple then
    ShowMessage('An Apple')
  else if AFruit is TPear then
    ShowMessage('A Pear')
  else
    ShowMessage('What is it?');
end;
Daniel Luyo
+1, because using virtual methods to replace the "is" operator is evil (ergh, the dark side?). Amongst other things, replacing "is" with virtual methods requires the PARENT class to have knowledge of every single CHILD class that will ever exist. If you ever start eating Avocados, simply creating the TAvocado class is not enough, you need to add a IsAvocado method to the parent class.
Cosmin Prund
I dare to say that "normally" methods like IsAvocado aren't needed and should be considered a code smell. Of course there are exceptions yada yada yada... :-)
Ulrich Gerhardt
+1  A: 

Here is an example for the use of virtual methods:

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

TPear = class(TFruit)
public
  procedure doSomethingFruitSpecific; override;
end;

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

TMain = class
   procedure SetFruit( Fruit : TFruit ); 
end;

implementation

procedure TMain.SetFruit( Fruit : TFruit );
begin
  Fruit.doSomethingFruitSpecific;
end;

procedure TApple.doSomethingFruitSpecific;
begin
  Writeln('bake an apple pie');
end;

procedure TPear.doSomethingFruitSpecific;
begin
  Writeln('pick some pears');
end;
splash