Reputation: 197
I am having a hard time implementing multi-tier inheritance from the basic TThread class.
Based on my knowledge of OOP it should be possible but maybe it just can't be applied to threads.
My goal is to use TMyBaseThread to implement all the code that will be common to descendent classes. This is what I have have tried:
TMyBaseThread = class(TThread)
private
procedure BuildBaseObjects(aParam : TParam);
procedure Execute; virtual; abstract;
protected
constructor Create(Param : TParam); reintroduce; virtual;
end;
TMyFileThread = class(TMyBaseThread)
private
procedure Execute; reintroduce;
public
constructor Create(OtherParam : TOtherParam); reintroduce; overload;
end;
TMyDBThread = class(TMyBaseThread)
private
procedure Execute; reintroduce;
public
constructor Create(aDB : TDatabase); reintroduce; overload;
end;
implementation
constructor TMyBaseThread.Create(Param : TParam);
begin
inherited Create(False);
Self.BuildBaseObjects(Param);
[do some stuff]
end;
constructor TMyFileThread.Create(OtherParam : TOtherParam);
var
param : TParam;
begin
inherited Create(param);
[do some other stuff]
end;
procedure TMyFileThread.Execute;
begin
while not Terminated do
doWork(); <-- this is never called
end;
constructor TMyDBThread.Create(aDB : TDatabase);
var
param : TParam;
begin
inherited Create(param);
end;
procedure TMyDBThread.Execute;
begin
while not Terminated do
doDatabaseWork(); <-- this is never called
end;
I see in TThread's implementation that the Executed method is called automatically in the AfterConstruction but how can I have it point to the one declared in the derived classes?
Thanks!
Upvotes: 2
Views: 1859
Reputation: 5448
First, I can't support more Craig's comment about using composition instead of inheritance for implementing the common functionality.
And although the architecture choice is under question, there is a lot to learn from your example.
Before inheriting a class, you should investigate the interface of the parent class that you want to inherit. To do that you can either look for the class definition in the interface section of the source code or look up the relevant documentation - System.Classes.TThread
.
It seems you have already read the documentation, so let's take a look at an except from the class definition of TThread
:
TThread = class
private
...
protected
procedure CheckThreadError(ErrCode: Integer); overload;
procedure CheckThreadError(Success: Boolean); overload;
procedure DoTerminate; virtual;
procedure Execute; virtual; abstract;
procedure Queue(AMethod: TThreadMethod); overload;
procedure Synchronize(AMethod: TThreadMethod); overload;
property ReturnValue: Integer read FReturnValue write FReturnValue;
property Terminated: Boolean read FTerminated;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
procedure AfterConstruction; override;
procedure Resume;
procedure Suspend;
procedure Terminate;
function WaitFor: LongWord;
class procedure Queue(AThread: TThread; AMethod: TThreadMethod); overload;
class procedure RemoveQueuedEvents(AThread: TThread; AMethod: TThreadMethod);
class procedure StaticQueue(AThread: TThread; AMethod: TThreadMethod);
class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;
class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod);
property FatalException: TObject read FFatalException;
property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;
property Handle: THandle read FHandle;
property Priority: TThreadPriority read GetPriority write SetPriority;
property Suspended: Boolean read FSuspended write SetSuspended;
property ThreadID: THandle read FThreadID;
property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
end;
First, ignore anything that is in the private
section of the class. If those fields and methods were marked as private
, we are not supposed to be able to use them at all in descendant classes.
Then, look for any abstract
methods. The implementation of abstract methods is left for descendant classes. So those are the methods you are expected to implement in your code. Abstract methods are usually called indirectly by using one of the methods from the parent class.
In your case the TThread
class has only one abstract method:
procedure Execute; virtual; abstract;
The documentation says that you need to
Define the thread object's Execute method by inserting the code that should execute when the thread is executed.
It's true that the documentation sounds a bit vague, but the right way to do that is to "override" the method in the interface, and not to "reintroduce" it:
TMyFileThread = class(TMyBaseThread)
...
protected
procedure Execute; override;
...
and then implement it in the implementation:
procedure TMyFileThread.Execute;
begin
while not Terminated do
Sleep(1); // do some other stuff
end;
You probably notice how we declared the overrided definition of Execute
method in the protected
section. This is required as the definition of the method in the parent class is also in the protected
section, so we can only override it in a section with higher visibility (protected
or public
).
You rarely need to increase the visibility when overriding a method, so we just keep the same visibility.
We used the override
keyword to tell the base class to use this variant of the method instead of its own. If you miss the override
keyword the Execute
method will not be called at all, and the base class will try calling it's own Execute method, if there is any.
Another thing to note is that you don't need to redeclare the Execute
method in your base class as you are not implementing it there. That's why you should remove the following definition:
TMyBaseThread = class(TThread)
...
//procedure Execute; virtual; abstract; <- remove this
...
The execute method is already defined in the TThread
class.
Now, let's look at the constructors. The base class has a regular constructor that is neither virtual
, nor dynamic
:
public
constructor Create(CreateSuspended: Boolean);
This means you cannot override those constructors and if you want to add additional logic on object creation, you should create your own constructors that wrap those.
The proper way to do that is to just declare a constructor with a different set of parameters, without reintroducing, overloading or overriding the base one:
public
//constructor Create(Param : TParam); reintroduce; virtual;
constructor Create(Param : TParam);
Also, remember that constructors should almost always be in the public
section.
You also don't need to make the constructor virtual
. You could do that if your TMyFileThread
and TMyDBThread
classes needed to add some logic inside the constructor, without changing the constructor parameters.
When you change the set of parameters all that is required is that you call the inherited constructor as first thing inside the new one:
constructor TMyFileThread.Create(OtherParam : TOtherParam);
var
param : TParam;
begin
inherited Create(param); // It is enough to call the base constructor at the top
// do some other stuff
end;
The definition requires no keywords:
TMyFileThread = class(TMyBaseThread)
...
public
constructor Create(OtherParam : TOtherParam);
Did you notice how we used inherited Create(param)
to call the base constructor, but we did not use inherited Execute;
? That's because the Execute
method was marked as abstract
and have no default implementation in the base class. Using inherited on an abstract method would cause an exception as there is no default method to call.
As a general rule calling inherited Create
is a must if the base constructor you are calling is marked as virtual
, but is almost always required even if it is not marked so.
Finally, I want to formulate a summary of the relevant keywords and their most common use:
virtual
. Can save some memory resources, but only if the method is not reimplemented in all descendant classes and there are many objects created from such classes. Rarely used.inherited
keyword inside your implementation to call the base method.virtual
, override
keywords. Although sometimes you can combine both effects.Having said that, here is my interpretation of your code:
interface
uses
Classes, SysUtils;
type
TParam = class
end;
TOtherParam = class
end;
TDatabase = class
end;
TMyBaseThread = class(TThread)
private
procedure BuildBaseObjects(aParam : TParam);
protected
public
constructor Create(Param : TParam);
end;
TMyFileThread = class(TMyBaseThread)
private
protected
procedure Execute; override;
public
constructor Create(OtherParam : TOtherParam);
end;
TMyDBThread = class(TMyBaseThread)
private
protected
procedure Execute; override;
public
constructor Create(aDB : TDatabase);
end;
implementation
{ TMyBaseThread }
constructor TMyBaseThread.Create(Param : TParam);
begin
inherited Create(False);
Self.BuildBaseObjects(Param);
// Do some stuff
end;
procedure TMyBaseThread.BuildBaseObjects(aParam : TParam);
begin
// Do some stuff
end;
{ TMyFileThread }
constructor TMyFileThread.Create(OtherParam : TOtherParam);
var
param : TParam;
begin
inherited Create(param); // Remember to initialize param somehow
// Do some other stuff
end;
procedure TMyFileThread.Execute;
begin
while not Terminated do
Sleep(1);
end;
{ TMyDBThread }
constructor TMyDBThread.Create(aDB : TDatabase);
var
param : TParam;
begin
inherited Create(param); // Remember to initialize param somehow
end;
procedure TMyDBThread.Execute;
begin
while not Terminated do
Sleep(1);
end;
PS. Actually using inheritance on TThread
is mostly useful for plugin architectures or task workers. You can look up for examples on that.
Upvotes: 15