Delphi:更好的设计避免了循环单元引用



我在Delphi 10中有一个三角形网格结构。

出于性能原因,我将网格顶点、三角形面等的数据存储在TList的子代中。

我让TLists为列表中的每个成员进行计算。对于这些计算,我需要访问TMesh结构的一些字段。因此,在创建TMesh以及随后创建列表的过程中,我将父TMesh分配给列表。我使用TMesh的正向声明来做到这一点。请参阅以下代码:

type
  {forward declaration}
  TMesh=class;
  TVertex=record
    Point: TPoint3D;
    //other fields
  end;
  TVertices=class(TList<TVertex>)
    Mesh: TMesh;
    procedure DoSomethingWithAllVertices; //uses some fields of TMesh
    constructor Create(const AMesh: TMesh);
    //other methods
  end;
  TTriangleFace=record
    Vertices: Array[0..2] of Integer;
    //other fields
  end;
  TTriangleFaces=class(TList<TTriangleFace>)
    Mesh: TMesh;
    procedure DoSomethingWithAllTriangleFaces; //uses some fields of TMesh
    constructor Create(const AMesh: TMesh);
    //other methods
  end;
  TMesh=class(TComponent)
    Vertices: TVertices;
    TriangleFaces: TTriangleFaces;
    constructor Create(AOwner: TComponent);
    //other fields & methods
  end;
implementation
constructor TMesh.Create(AOwner: TComponent);
begin
  inherited;
  Vertices:=TVertices.Create(Self);
  TriangleFaces:=TTriangleFaces.Create(Self);
end;
constructor TVertices.Create(const AMesh: TMesh);
begin
  Mesh:=AMesh;
end;

这很好用。

然而,由于我的项目不断增长,我得到了越来越多的代码,我想将列表类分布在不同的单元中。这导致了循环单元引用的问题。

循环单元引用的问题似乎是众所周知的。我检查了可能的解决方案,但找不到一个似乎适合我的问题的。有人说,如果你遇到循环单元引用,代码设计得很糟糕。

如何改进设计,同时保持较高的计算性能?

解决这个问题的其他可能性是什么?

非常感谢!

Forward声明不能跨单元工作。当一个单元正向声明一个记录/类时,同一个单元也必须定义该记录/类。

我建议定义TMesh实现的IMesh接口,然后让TVerticesTTriangleFaces直接使用IMesh而不是TMesh。这样,就没有循环引用,并且接口可以公开所需的任何字段值的属性。TComponent禁用已实现接口的引用计数,因此内存泄漏不是问题。

MeshIntf.pas:

unit MeshIntf;
interface
type
  IMesh = interface(IInterface)
    ['{30315BC6-9A2E-4430-96BB-297D11C9DB5D}']
    // methods for performing common tasks...
    // properties for reading/setting needed values...
  end;
implementation
end.

垂直.pas:

unit Vertices;
interface
uses
  System.Types, System.Generics.Collections, MeshIntf;
type
  TVertex = record
    Point: TPoint3D;
    //other fields
  end;
  TVertices = class(TList<TVertex>)
  public
    Mesh: IMesh;
    constructor Create(const AMesh: IMesh); reintroduce;
    procedure DoSomethingWithAllVertices;
    //other methods
  end;
implementation
constructor TVertices.Create(const AMesh: IMesh);
begin
  inherited Create;
  Mesh := AMesh;
end;
procedure TVertices.DoSomethingWithAllVertices;
begin
  // use properties/methods of Mesh as needed...
end;
end.

三角形面.pas:

unit TriangleFaces;
interface
uses
  System.Generics.Collections, MeshIntf;
type
  TTriangleFace = record
    Vertices: Array[0..2] of Integer;
    //other fields
  end;
  TTriangleFaces = class(TList<TTriangleFace>)
  public
    Mesh: IMesh;
    constructor Create(const AMesh: IMesh); reintroduce;
    procedure DoSomethingWithAllTriangleFaces;
    //other methods
  end;
implementation
constructor TTriangleFaces.Create(const AMesh: IMesh);
begin
  inherited Create;
  Mesh := AMesh;
end;
procedure TTriangleFaces.DoSomethingWithAllTriangleFaces;
begin
  // use properties/methods of Mesh as needed...
end;
end.

网格.pas:

unit Mesh;
interface
uses
  Classes, MeshIntf, Vertices, TriangleFaces;
type
  TMesh = class(TComponent, IMesh)
  public
    Vertices: TVertices;
    TriangleFaces: TTriangleFaces;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    //other fields & methods, and IMesh implementation
  end;
implementation
constructor TMesh.Create(AOwner: TComponent);
begin
  inherited;
  Vertices := TVertices.Create(Self as IMesh);
  TriangleFaces := TTriangleFaces.Create(Self as IMesh);
end;
destructor TMesh.Destroy;
begin
  Vertices.Free;
  TriangleFaces.Free;
  inherited;
end;
end.

如果您不需要TMesh在设计时可用于表单设计器和对象检查器,则应该从TInterfacedObject而不是TComponent派生它。但是,您需要进行一些小的调整,以正确处理引用计数(TComponent禁用)。特别是,TVerticesTTriangleFaces需要使用弱引用,以免增加TMesh的引用计数并导致内存泄漏(因为在这种情况下,其引用计数将降至0):

MeshIntf.pas:

unit MeshIntf;
interface
uses
  System.Types;
type
  TVertex = record
    Point: TPoint3D;
    //other fields
  end;
  IVertices = interface(IInterface)
    ['{97A70A11-C8B6-4DBC-807B-B9E0C6953B9E}']
    // methods for performing tasks...
    procedure DoSomethingWithAllVertices;
    function GetVertex(Index: Integer): TVertex;
    // properties for reading/setting values...
    property Vertex[Index: Integer]: TVertex read GetVertex;
  end;
  TTriangleFace = record
    Vertices: Array[0..2] of Integer;
    //other fields
  end;
  ITriangleFaces = interface(IInterface)
    ['{A1ED479B-7430-4524-A630-FDDE212375BB}']
    // methods for performing tasks...
    procedure DoSomethingWithAllTriangleFaces;
    function GetFace(Index: Integer): TTriangleFace;
    // properties for reading/setting values...
    property Face[Index: Integer]: TTriangleFace read GetFace;
  end;
  IMesh = interface(IInterface)
    ['{30315BC6-9A2E-4430-96BB-297D11C9DB5D}']
    // methods for performing common tasks...
    function GetVertices: IVertices;
    function GetTriangleFaces: ITriangleFaces;
    // properties for reading/setting values...
    property Vertices: IVertices read GetVertices;
    property TriangleFaces: ITriangleFaces read GetTriangleFaces;
  end;
implementation
end.

垂直.pas:

unit Vertices;
interface
uses
  System.Generics.Collections, MeshIntf;
type
  TVertices = class(TInterfacedObject, IVertices)
  private
    // Delphi 10.1 Berlin adds [weak] support to all compilers,
    // it was previously only available on the mobile compilers...
    {$IFDEF WEAKINTFREF}
    [weak] fMesh: IMesh;
    {$ELSE}
    fMesh: Pointer;
    {$ENDIF}
    fVertices: TList<TVertex>;
  public
    constructor Create(AMesh: IMesh);
    destructor Destroy; override;
    //other methods
    // IVertices implementation
    procedure DoSomethingWithAllVertices;
    function GetVertex(Index: Integer): TVertex;
  end;
implementation
constructor TVertices.Create(AMesh: IMesh);
begin
  inherited Create;
  fMesh := {$IFDEF WEAKINTFREF}AMesh{$ELSE}Pointer(AMesh){$ENDIF};
  fVertices := TList<TVertex>.Create;
end;
destructor TVertices.Destroy;
begin
  fVertices.Free;
  inherited;
end;
procedure TVertices.DoSomethingWithAllVertices;
begin
  // use properties of fMesh as needed...
  // if WEAKINTFREF is not defined simply type-cast the Mesh
  // pointer as IMesh(fMesh) when accessing its members...
end;
function TVertices.GetVertex(Index: Integer): TVertex;
begin
  Result := fVertices[Index];
end;
end.

三角形面.pas:

unit TriangleFaces;
interface
uses
  System.Generics.Collections, MeshIntf;
type
  TTriangleFaces = class(TInterfacedObject, ITriangleFaces)
  private
    // Delphi 10.1 Berlin adds [weak] support to all compilers,
    // it was previously only available on the mobile compilers...
    {$IFDEF WEAKINTFREF}
    [weak] fMesh: IMesh;
    {$ELSE}
    fMesh: Pointer;
    {$ENDIF}
    fFaces: TList<TTriangleFace>;
  public
    constructor Create(AMesh: IMesh);
    destructor Destroy; override;
    //other methods
    // ITriangleFaces implementation
    procedure DoSomethingWithAllTriangleFaces;
    function GetFace(Index: Integer): TTriangleFace;
  end;
implementation
constructor TTriangleFaces.Create(AMesh: IMesh);
begin
  inherited Create;
  fMesh := {$IFDEF WEAKINTFREF}AMesh{$ELSE}Pointer(AMesh){$ENDIF};
  fFaces := TList<TTriangleFace>.Create;
end;
destructor TTriangleFaces.Destroy;
begin
  fFaces.Free;
  inherited;
end;
procedure TTriangleFaces.DoSomethingWithAllTriangleFaces;
begin
  // use properties of fMesh as needed...
  // if WEAKINTFREF is not defined simply type-cast the Mesh
  // pointer as IMesh(fMesh) when accessing its members...
end;
function TTriangleFaces.GetFace(Index: Integer): TTriangleFace;
begin
  Result := fFaces[Index];
end;
end.

网格.pas:

unit Mesh;
interface
uses
  MeshIntf;
type
  TMesh = class(TInterfacedObject, IMesh)
  private
    // note, these are *strong* references, not*weak* references!
    fVertices: IVertices;
    fTriangleFaces: ITriangleFaces;
  public
    constructor Create;
    //other fields & methods
    // IMesh implementation
    function GetVertices: IVertices;
    function GetTriangleFaces: ITriangleFaces;
  end;
implementation
uses
  Vertices, TriangleFaces;
constructor TMesh.Create;
begin
  inherited;
  fVertices := TVertices.Create(Self as IMesh);
  fTriangleFaces := TTriangleFaces.Create(Self as IMesh);
end;
function TMesh.GetVertices: IVertices;
begin
  Result := fVertices;
end;
function TMesh.GetTriangleFaces: ITriangleFaces;
begin
  Result := fTriangleFaces;
end;
end.

在创建TMesh对象时,只需确保代码中的某个位置有一个非弱IMesh变量,使其保持活动状态,直到您不再需要它:

var
  Meth: IMesh; // or a class member or a global, wherever you need it
Mesh := TMesh.Create;
...
Mesh := nil;

(适当的)参考计数将为您解决其余问题。

您当前的解决方案已经是解决问题的最佳方法。将这些类型拆分为单独的单元会造成重大障碍,并导致代码变得笨拙。

虽然我可以理解您希望将这些类型分开的愿望,但您必须在这种愿望与生成的代码的清晰度之间取得平衡。在这种情况下,分裂的负面后果远远超过了积极因素。保持代码原样。

最新更新