3

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?

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
AmigoJack
  • 5,234
  • 1
  • 15
  • 31
  • 4
    You need to add more information here. `Terminate` is not virtual and so when you say that you "override `Terminate`" that cannot be true. You cannot override a non-virtual method. You should show the implementation of your `Terminate` methods which *hide* rather than *override*. The correct solution will be, of course, to call a virtual method. What that virtual method should be we cannot say. It may be that you should call `Terminate` but override `TermaintedSet`. But there is insufficient detail in the question for us to say. Certainly your final two paragraphs are not the solution! – David Heffernan Mar 02 '16 at 10:14
  • I know "override" is a keyword, yet I meant it in non-technical terms, like redefining, or yes: "hide". Augmented class definition details. – AmigoJack Mar 02 '16 at 10:40
  • Your edit doesn't help because we cannot see what these `Terminate` methods do. It's really important to use terminology accurately. – David Heffernan Mar 02 '16 at 10:44
  • If you want to **override** a method in a child class then you must declare the method in the parent class as **virtual** and in the child class as **override**. Inside the child class you can call the parent method with **inherited** but you do not have to. – Sir Rufo Mar 02 '16 at 10:44
  • Hiding or replacing means that the method will not be called on a child class. For that, the virtual and dynamic mechanisms can be used, and then, you really override. – Rudy Velthuis Mar 02 '16 at 11:00
  • " And defining the method as virtual also won't fully work, as every child level could be the "last" one. Or not." -- ISTM that defining as virtual is exactly what you need. – Rudy Velthuis Mar 02 '16 at 11:02
  • @David Heffernan: Why is the content of each `.Terminate()` relevant? They all do different stuff which can't be generalized - so can you explain how such an information should help?. – AmigoJack Mar 02 '16 at 11:20
  • Because I want to know whether or not you should override `TerminatedSet`, or whether you should introduce a new virtual method with a name other than `Terminate`. I think you'll get far better help if you cooperate. – David Heffernan Mar 02 '16 at 11:34
  • Anyway, I've written an answer that covers both options. Either `TThread` already has a virtual method that you can override, or you need to introduce a new virtual method. – David Heffernan Mar 02 '16 at 11:46

1 Answers1

5

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • My `TThread` definition is much older (Delphi 7): `procedure TThread.Terminate; begin FTerminated := True; end;` But with your answer I now also understand your first comments, and it gives me at least one new idea I could try: I'll create a TThread child which supports that and then use that as the most common demoninator, which should then mean calling `(TThreadPlus.Create).TerminateSet();` will go to the right code, since `TThreadPlus.TerminateSet()` is always virtual. – AmigoJack Mar 02 '16 at 14:08
  • Virtual method dispatch is surely the key. And I think that leaves two options. Either override an existing virtual if a good candidate exists. Or introduce your own. So I believe all you need is in the answer. – David Heffernan Mar 02 '16 at 14:14
  • This really helped me: a `.Terminate()` method which will always be called on the parent class (generalization), which itself calls the virtual `.TerminatedSet()` and thus always lands on the child's end. Never thought about such a combination. Thanks, David. – AmigoJack Mar 02 '16 at 15:25