在Delphi中绕过循环引用

时间:2010-04-15 12:00:22

标签: delphi circular-reference

有没有办法在Delphi中绕过循环单元引用?

也许是较新版本的delphi或某些魔法黑客还是什么?

我的delphi项目有10万多行代码,主要基于单例类。我需要重构这个,但这意味着几个月的“循环引用”地狱:)

8 个答案:

答案 0 :(得分:40)

过去10年来,我一直在维护近百万行遗留代码,所以我理解你的痛苦!

在我维护的代码中,当我遇到循环使用时,我经常发现它们是由单元A中单元A所需的常量或类型定义引起的。(有时候它也是一小段代码单元A中的(甚至是全局变量),单元B也需要。

在这种情况下(当我很幸运时!)我可以仔细地将代码的这些部分提取到一个包含常量,类型定义和共享代码的新单元C.然后单元A和B使用单元C.

我发布上述内容时有些犹豫,因为我不是软件设计方面的专家,并且意识到这里有许多其他人比我更了解情况。但是,希望我的经验会对你有所帮助。

答案 1 :(得分:13)

  1. 看来,你有很严重的代码设计问题。除了这些问题的许多迹象,一个是单位循环参考。但正如你所说 - 你无法重构所有代码。
  2. 将所有可能的内容移至“实施”部分。他们被允许有循环引用。
  3. 要简化任务(2),您可以使用3d派对工具。我建议 - Peganza Pascal Analyzer(http://www.peganza.com)。它将建议您可以移动到实现部分。为您提供更多提示,以提高您的代码质量。

答案 2 :(得分:9)

尽可能使用实现部分,并将接口使用子句中的内容限制为具有在接口声明中可见。

没有“神奇的黑客”。循环引用会导致编译器无限循环(单元A需要编译单元B,这需要编译单元A,这需要编译单元B等)。

如果您有一个特定的实例,您认为无法避免循环引用,请编辑您的帖子并提供代码;我相信这里有人可以帮助你弄清楚如何修复它。

答案 3 :(得分:7)

有许多方法可以避免循环引用。

  1. 委托。 通常,对象将执行一些应该在事件中完成的代码,而不是由对象本身完成。无论是因为项目工程师的工作时间太短(不是我们总是?),没有足够的经验/知识或者只是懒惰,这样的代码最终会在应用程序中出现。真实世界的例子:TCPSocket组件直接更新应用程序的MainForm上的一些可视组件,而不是让主窗体在组件上注册“OnTCPActivity”过程。

  2. 抽象类/接口。使用其中任何一个都可以消除许多单元之间的直接依赖关系。抽象类或接口可以在其自己的单元中单独声明,从而将依赖性限制为最大值。例子:我们的应用程序有一个调试表单。它几乎用于整个应用程序,因为它显示来自应用程序各个区域的信息。更糟糕的是,每个允许显示调试表单的表单也最终都需要调试表单中的所有单元。一种更好的方法是使用一个基本上为空的调试表单,但它具有注册“DebugFrames”的能力。

    TDebugFrm.RegisterDebugFrame(Frame:TDebugFrame);

    这样,TDebugFrm没有自己的依赖(除了在TDebugFrame类上)。需要显示调试表单的任何和所有单元都可以这样做,而不会冒太多依赖项的风险。

  3. 还有很多其他例子......我打赌它可以填补自己的一本书。以时间有效的方式设计干净的类层次结构非常困难,并且它具有经验。了解可用于实现它的工具以及如何使用它是实现它的第一步。但要回答你的问题......对你的问题没有一个尺寸适合所有答案,它总是根据具体情况进行。

答案 4 :(得分:4)

类似问题:Delphi Enterprise: how can I apply the Visitor Pattern without circular references?

Uwe Raabe提出的解决方案使用接口来解决循环依赖。

答案 5 :(得分:1)

Modelmaker Code Explorer有一个非常好的向导,可列出所有用途,包括周期。

它要求您的项目编译。

我同意其他海报的说法,这是一个设计问题 您应该仔细查看您的设计,并删除未使用的单位。

在DelphiLive'09上,我做了一个名为Smarter code with Databases and data aware controls的会议,其中包含很少有关良好设计的提示(不限于数据库应用程序)。

- 的Jeroen

答案 6 :(得分:1)

我找到了一个不需要使用接口的解决方案,但可能无法解决循环引用的每个问题。

我有两个单元,分为两个单元:TMap和TTile。

TMap包含一张地图,并使用等距图块(TTile)显示它。

我想在TTile中有一个指针指向地图。 Map是TTile的类属性。

  

Class Var FoMap:TObject;

Normaly,您需要在另一个单元中声明每个相应的单位...并获得循环参考。

在这里,我是如何解决它的。

在TTile中,我将map声明为TObject并在Implementation部分的Uses子句中移动Map单元。

这样我可以使用map,但每次都需要将它转换为TMap来访问它的属性。

我可以做得更好吗?如果我可以使用getter函数来输入它。但我需要在界面部分中移动Uses Map ....所以,回到原点。

在Implementation部分中,我确实声明了一个不属于我的类的getter函数。一个简单的功能。

  

实施

     

使用地图;

     

功能图:TMap;   开始     结果:= TMap(TTile.Map);   端;

很酷,我想。现在,每次我需要调用Map的属性时,我只使用Map.MyProperty。

哎哟!编译了! :)没有按预期的方式工作。编译器使用TTile的Map属性而不是我的函数。

所以,我将我的函数重命名为aMap。但我的缪斯跟我说话。真是没有!将类属性重命名为aMap ...现在我可以按照我想要的方式使用Map。

Map.Size;这称我的小函数,它将aMap类型化为TMap;

Patrick Forest

答案 7 :(得分:0)

我给出了一个先前的答案,但经过一些思考和刮擦后,我找到了一种更好的方法来解决循环参考问题。这里我的第一个需要在对象TB上指针的单元在单元B中定义。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, b, StdCtrls;

type

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }

  public
    { Public declarations }
    FoB: TB;
  end;

var
  Form1: TForm1;



implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  FoB := TB.Create(Self);
  showmessage(FoB.owner.name);
end;

end.

这里是单位B的代码,其中TB在TForm1上有一个指针。

unit B;

interface

  Uses
    dialogs, Forms;

  type
    TForm1 = class(TForm);

    TB = class
     private
       FaOwner: TForm1;
     public
       constructor Create(aOwner: TForm);
       property owner: TForm1 read FaOwner;
    end;

implementation
  uses unit1;

  Constructor TB.create(aOwner: TForm);
  Begin
    FaOwner := TForm1(aOwner);

    FaOwner.Left := 500;
  End;//Constructor
end.

这就是为什么它编译。第一单元B在实施部分宣布使用Unit1。立即解决Unit1和Unit B之间的循环参考单元。

但是为了允许Delphi编译,我需要给他一些东西来咀嚼FaOwner:TForm1的声明。因此,我添加了与Unit1中TForm1的声明匹配的存根类名称TForm1。 接下来,当调用构造函数时,TForm1能够传递自己的参数。在构造函数代码中,我需要将aOwner参数类型转换为Unit1.TForm1。瞧,FaOwner他指出我的形式。

现在,如果TB类需要在内部使用FaOwner,我不需要每次都对它进行类型转换 到Unit1.TForm1,因为两个声明是相同的。请注意,您可以将构造函数的声明设置为

Constructor TB.create(aOwner: TForm1); 但是当TForm1将调用构造函数并且传递自身有一个参数时,你需要对它进行类型转换它有b.TForm1。否则Delphi会抛出错误,告知TForm1不兼容。因此,每次调用TB.constructor时,都需要对相应的TForm1进行类型转换。第一个解决方案,使用共同的祖先,他的更好。写一次类型转换并忘掉它。

在我发布之后,我意识到我错误地说两个TForm1都是相同的。它们不是Unit1.TForm1具有B.TForm1未知的组件和方法。长期结核病不需要使用它们或只需要使用TForm给出的通用性你没关系。如果你需要从TB调用UNit1.TForm1特有的东西,你需要将它强制转换为Unit1.TForm1。

我尝试使用Delphi 2010测试它并编译和工作。

希望它会有所帮助,让你头疼。