Reputation: 6099
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
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