TThread和继承

时间:2017-11-11 05:37:17

标签: multithreading oop delphi inheritance

我很难从基本的TThread类实现多层继承 基于我对OOP的了解应该是可能的,但也许它不能应用于线程 我的目标是使用TMyBaseThread来实现后代类通用的所有代码。这就是我的尝试:

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;

我在TThread的实现中看到,在 AfterConstruction 中会自动调用Executed方法,但是如何让它指向派生类中声明的方法呢?

谢谢!

1 个答案:

答案 0 :(得分:12)

首先,我不能支持更多关于使用合成而不是继承来实现常用功能的Craig评论。

尽管架构选择存在疑问,但您可以从您的示例中学到很多东西。

在继承类之前,您应该研究要继承的父类的接口。为此,您可以在源代码的界面部分查找类定义,也可以查找相关文档 - System.Classes.TThread

您似乎已经阅读了文档,所以让我们看一下除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;

首先,忽略课程private部分中的任何内容。如果这些字段和方法被标记为private,我们就不应该在后代类中使用它们。

然后,查找任何abstract方法。抽象方法的实现留给了后代类。所以这些是您希望在代码中实现的方法。抽象方法通常使用父类中的一种方法称为间接

在您的情况下,TThread类只有一个抽象方法:

procedure Execute; virtual; abstract;

文档说你需要

  

通过插入代码来定义线程对象的Execute方法   应该在执行线程时执行。

确实文档听起来有点模糊,但正确的方法是&#34;覆盖&#34; 界面中的方法,而不是&#34;再引入&#34;它:

  TMyFileThread = class(TMyBaseThread)
  ...
  protected
      procedure Execute; override;
  ...

然后在实现中实现它:

procedure TMyFileThread.Execute;
begin
    while not Terminated do
      Sleep(1); // do some other stuff
end;

您可能会注意到我们如何在Execute部分中声明了protected方法的覆盖定义。这是必需的,因为父类中的方法定义也在protected部分中,因此我们只能在具有更高可见性(protectedpublic)的部分中覆盖它。

在覆盖方法时,您很少需要提高可见性,因此我们只保持相同的可见性。

我们使用override关键字告诉基类使用此方法的变体而不是它自己的变体。如果您错过override关键字,则根本不会调用Execute方法,并且基类会尝试调用它自己的Execute方法(如果有的话)。

需要注意的另一点是,您不需要在基类中重新声明Execute方法,因为您没有在那里实现它。这就是为什么你应该删除以下定义:

TMyBaseThread = class(TThread)
...
    //procedure Execute; virtual; abstract;  <- remove this
...

执行方法已在TThread类中定义。

现在,让我们看看构造函数。基类有一个常规构造函数,既不是virtual,也不是dynamic

  public
    constructor Create(CreateSuspended: Boolean);

这意味着你不能覆盖那些构造函数,如果你想在对象创建时添加额外的逻辑,你应该创建自己的构造函数来包装它们。

执行此操作的正确方法是仅使用不同的参数集声明构造函数,而无需重新引入,重载或覆盖基本参数:

public
      //constructor Create(Param : TParam); reintroduce; virtual;
      constructor Create(Param : TParam);

另外,请记住,构造函数应该几乎总是位于public部分。

您也不需要制作构造函数virtual。如果您的TMyFileThreadTMyDBThread类需要在构造函数中添加一些逻辑,而不用更改构造函数参数,则可以这样做。

当您更改参数集时,只需将继承的构造函数作为新内容中的第一个内容调用:

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;

该定义不需要关键字:

  TMyFileThread = class(TMyBaseThread)
  ...
  public
      constructor Create(OtherParam : TOtherParam);

您是否注意到我们如何使用inherited Create(param)来调用基本构造函数,但我们没有使用inherited Execute;?这是因为Execute方法被标记为abstract并且在基类中没有默认实现。在抽象方法上使用inherited会导致异常,因为没有默认方法可以调用。

作为一般规则,如果您调用的基本构造函数标记为inherited Create,则调用virtual必须,但即使未标记,也几乎总是需要如此。

最后,我想制定相关关键字及其最常见用途的摘要:

  • 虚拟 - 声明可以在后代类中重新实现的方法。那是后代类可以改变方法的行为。他们可以用自己的代码替换其中的所有代码,或者可以调用基本方法,然后在之前/之后添加其他行为。此关键字仅在首次定义方法的基类中使用。后裔类使用其他关键字。
  • 动态 - 请将其视为与virtual相同。可以节省一些内存资源,但前提是该方法未在所有后代类中重新实现,并且有许多对象是从这些类创建的。很少使用。
  • 覆盖 - 声明一个方法,该方法通过提供替换基类中的默认代码来重新实现基类。您在后代类中使用override。您可以在实现中使用inherited关键字来调用基本方法。
  • 重载 - 使用另一组参数声明方法的替代变体。请注意,重载方法与继承无关。当基类调用原始方法时,不会执行该方法的重载版本。通常重载方法规范化参数(将它们转换为其他类型,为某些参数添加默认值),然后调用其他重载方法之一。同样,这与继承和virtualoverride关键字无关。虽然有时你可以结合两种效果。
  • 重新引入 - 这又不是继承。您在后代类中使用此关键字。目的是从类内部调用方法(并且只调用类,而不是来自基类的调用)来执行方法的重新引入版本而不是基类。来自基类的调用仍然执行原始版本。很少使用。

话虽如此,这是我对你的代码的解释:

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。实际上在TThread上使用继承对于插件体系结构或任务工作者来说非常有用。您可以查看相关示例。