AmigoJack
AmigoJack

Reputation: 6099

Calling child class non-virtual method or setting child class property

I have a base class TThread which has child classes like TThreadSock and TThreadPool, which override the .Terminate() method. And childs of those childs (like TThreadSockDownload or TThreadPoolCollect) inherite those .Terminate() methods (or might even override them):

type
  TThreadSock= class( TThread )
    procedure Terminate;  // Hides TThread.Terminate
  end;
  TThreadSockDownload= class( TThreadSock );
  TThreadSockUpload= class( TThreadSock )
    procedure Terminate;  // Hides TThreadSock.Terminate
  end;

  TThreadPool= class( TThread )
    procedure Terminate;  // Hides TThread.Terminate
  end;
  TThreadPoolCollect= class( TThreadPool );

My problem is: I have a list which can contain everything, so the most common denominator is TThread. And from that base class I need to call the most "childish" .Terminate() method. Currently my approach is this:

var
  oThread: TThread;
begin
  oThread:= GetNextThread();

  if oThread is TThreadSockDownload then TThreadSockDownload(oThread).Terminate() else
  if oThread is TThreadSockUpload then TThreadSockUpload(oThread).Terminate() else
  if oThread is TThreadPoolCollect then TThreadPoolCollect(oThread).Terminate() else ...

...and you get an idea where this leads to. Not much to speak of that I have to use this code elsewhere as well. If I'd call oThread.Terminate() then the code of the base class is executed, which is not what I want. And defining the method as virtual also won't fully work, as every child level could be the "last" one. Or not.

My ultimate goal is to generalize this as much as possible, so I don't need to ask for each class as a candidate. Maybe I'm missing something fundamental here, like GetRealClass( oThread ).Call( 'Terminate' ); and GetRealClass( oThread ).Set( 'Stop', TRUE ); would be a dream.

Am I at least able to generalize this code so I only need to write it once? Something like FindMethod on an object I also have tell its class type to?

Upvotes: 3

Views: 582

Answers (1)

David Heffernan
David Heffernan

Reputation: 612993

The correct way to deal with this is to use a virtual method. This mechanism is designed to allow method dispatch based on the runtime type of an object. In other words, precisely your your laboured type checking code does.

But you are grappling with the fact that you want to name your method Terminate, which is the name of an existing method that is not virtual. So, how to get past that.

Well, if you decided on the name Terminate because your methods call the TThread.Terminate, and then do other tasks, then the framework provides you with a simple way out. Let's look at the implementation of TThread.Terminate.

procedure TThread.Terminate;
begin
  if FExternalThread then
    raise EThread.CreateRes(@SThreadExternalTerminate);
  FTerminated := True;
  TerminatedSet;
end;

Note the call to TerminatedSet. That is a virtual method whose implementation is like so:

procedure TThread.TerminatedSet;
begin
end;

It does nothing. It has been provided to allow you to override it in derived classes, and have it called whenever the non-virtual method Terminate is called.

So you would do this:

type
  TMyDerivedThread = class(TThread)
  protected
    procedure TerminatedSet; override;
  end;

....
procedure TMyDerivedThread.TerminatedSet; 
begin
  inherited;
  // do your class specific tasks here
end;

And then the code that controls the threads can call the non-virtual Terminate method, but still have this virtual method be called.

oThread := GetNextThread;
oThread.Terminate;

Now, on the other hand, it's plausible that your Terminate methods do not call TThread.Terminate. In which case the approach would be different. You still need a virtual method, but if the TThread class does not contain an appropriate virtual already, you need to introduce one. Which means deriving a new base class in order to introduce that virtual method.

type
  TBaseThread = class(TThread)
  public
    procedure MyTerminate; virtual; abstract;
  end;

I've made this abstract but you may not want to. We can't tell because we don't know what your thread implementations do. You can decide whether or not this method should be abstract.

Now you can override this virtual method like any other, which is something I believe you already understand. The other change you need to make is that instead of holding TThread references when operating on the thread instances, you hold TBaseThread references.

Upvotes: 5

Related Questions