3

My class definitions are :

TAnimal = class(TInterfacedObject)
public
    constructor Create; overload;
    constructor Create(param : string); overload;
end;

IAnimal = interface
    procedure DoSomething;
end;

TDog = class(TAnimal, IAnimal)
public
    procedure DoSomething;
end;

TCat = class(TAnimal, IAnimal)
public
    procedure DoSomething;
end;

Example code :

procedure TForm1.DogButtonPressed(Sender: TObject);
var
    myDog : TDog;
    I : Integer;
begin
    myDog := TDog.Create('123');
    I := Length(myQueue);
    SetLength(myQueue, I+1);
    myQueue[I] := TDog; //Probably not the way to do it...??
end;

procedure TForm1.CatButtonPressed(Sender: TObject);
var
    myCat : TCat;
    I : Integer;
begin
    myCat := TCat.Create('123');
    I := Length(myQueue);
    SetLength(myQueue, I+1);
    myQueue[I] := TCat; //Probably not the way to do it...??
end;

procedure TForm1.OnProcessQueueButtonPressed(Sender: TObject);
var
    MyInterface : IAnimal; //Interface variable
    I : Integer;
begin
    for I := Low(myQueue) to High(myQueue) do
    begin
        MyInterface := myQueue[I].Create('123'); //Create instance of relevant class
        MyInterface.DoSomething;
    end;
end;

So, let's say you have a form with three buttons on it. A "Dog" button, a "Cat" button and a "Process Queue" button. When you press either the "Dog" button or the "Cat" button, the relevant class is added to an array to act as a queue. When you then press the "Process Queue" button, the program steps through the array, creates an object of the relevant class, and then calls an interface method that is implemented in that class. Keeping my example code above in mind, how can this be accomplished?

The easy way would obviously be to add the class names as a string to an array of string and then use an if statement in the OnProcessQueueButtonPressed procedure, eg :

procedure TForm1.OnProcessQueueButtonPressed(Sender: TObject);
var
    MyInterface : IAnimal; //Interface variable
    I : Integer;
begin
    for I := Low(myQueue) to High(myQueue) do
    begin
        if myQueue[I] = 'TDog' then
            MyInterface := TDog.Create('123');
        if myQueue[I] = 'TCat' then
            MyInterface := TCat.Create('123');            
        MyInterface.DoSomething;
    end;
end;

I am trying to avoid this, because every time I add a new class I will have to remember to add an if block for the new class.

1
  • @J... Added an explicit question statement, instead of implying one. I am using Delphi Seattle in this case. Commented Feb 1, 2016 at 15:07

1 Answer 1

11

You can do this with a class reference. Define your class reference type like this:

type
  TAnimalClass = class of TAnimal;

And arrange that TAnimal supports the interface:

type
  IAnimal = interface
    procedure DoSomething;
  end;

  TAnimal = class(TInterfacedObject, IAnimal)
  public
    constructor Create; overload;
    constructor Create(param: string); overload;
    procedure DoSomething; virtual; abstract;
  end;

  TDog = class(TAnimal)
  public
    procedure DoSomething; override;
  end;

  TCat = class(TAnimal)
  public
    procedure DoSomething; override;
  end;

Your use of an array leads to rather messy code. Better would be to use a list object.

var
  myQueue: TList<TAnimalClass>; 

Now you can write the code like this:

procedure TForm1.DogButtonPressed(Sender: TObject);
begin
  myQueue.Add(TDog);
end;

procedure TForm1.CatButtonPressed(Sender: TObject);
begin
  myQueue.Add(TCat);
end;

procedure TForm1.OnProcessQueueButtonPressed(Sender: TObject);
var
  AnimalClass: TAnimalClass;
  Animal: IAnimal;
begin
  for AnimalClass in myQueue do
  begin
    Animal := AnimalClass.Create('123'); 
    Animal.DoSomething;
  end;
  myQueue.Clear;
end;

You'll need to create and destroy the instance of myQueue at an appropriate point. I am assuming you already know how to do that.

One finer nuance when using class references is that you normally provide a virtual constructor in the base class. That's because when you use a class reference to create an instance, you call the constructor declared in the base class. If that constructor is not virtual, then your derived class constructor code will not be executed.

Of course, using class references in this way renders the interface somewhat pointless.

Sign up to request clarification or add additional context in comments.

3 Comments

class reference are a true gem of the Delphi / object pascal language. I miss this feature so much in Java or C#!
Also please mention that using a virtual constructor Create does make sense, otherwise the implementation may come into limitations sooner or later.
@DavidHeffernan Thanks! My actual implementation is somewhat more complicated than this, but your answer was invaluable in helping me to get the base laid out correctly. Thanks for your detailed answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.