有没有方法可以解决Delphi中的循环单元引用问题?
也许是使用更新版本的Delphi或某种神奇的黑科技什么的?
我的Delphi项目有10万多行代码,大部分基于单例类。我需要重构它,但这意味着要经历几个月的“循环引用”地狱 :)
有没有方法可以解决Delphi中的循环单元引用问题?
也许是使用更新版本的Delphi或某种神奇的黑科技什么的?
我的Delphi项目有10万多行代码,大部分基于单例类。我需要重构它,但这意味着要经历几个月的“循环引用”地狱 :)
我过去10年一直在维护接近一百万行的遗留代码,因此我理解您的痛苦!
在我维护的代码中,当我遇到循环使用时,我经常发现是由单元A中的常量或类型定义导致的,这些常量或类型定义被单元B需要。(有时也是单元A中的一小段代码(甚至全局变量)也被单元B需要。)
在这种情况下(如果我运气好的话!),我可以仔细地将那些代码部分提取到一个新的单元C中,其中包含常量、类型定义和共享代码。然后单元A和单元B使用单元C。
我有些犹豫地发布上面的内容,因为我不是软件设计专家,意识到这里有许多比我更有知识的人。希望我的经验能对您有所帮助。
implementation
部分。它们被允许有循环引用。
尽可能使用实现部分使用的内容,并将接口使用子句中的内容限制为必须在接口声明中可见的内容。
没有“魔法黑客”。循环引用会导致编译器陷入无限循环(单元A需要编译单元B,单元B需要编译单元A,单元A需要编译单元B,等等)。
如果您有特定情况认为无法避免循环引用,请编辑您的帖子并提供代码;我相信这里的某个人可以帮助您解决问题。
Modelmaker Code Explorer有一个非常好的向导,可以列出所有使用情况,包括循环。
它要求您的项目编译通过。
我同意其他帖子中提到的这是一个设计问题。
您应该仔细查看您的设计,并删除未使用的单元。
在DelphiLive'09上,我做了一个名为Smarter code with Databases and data aware controls的会议,其中包含了一些关于良好设计的技巧(不仅限于DB应用程序)。
--jeroen
实现
使用 Map;
函数 Map: TMap; 开始 结果 := TMap(TTile.Map); 结束;
太好了,我想。现在,每次我需要调用我的地图属性时,我只需使用 Map.MyProperty。
哎呀!编译失败了! :) 没有按预期工作。编译器使用 TTile 的 Map 属性而不是我的函数。
所以,我将我的函数重命名为 aMap。但我的缪斯向我倾诉。不要!重命名类属性为 aMap... 现在我可以按照自己的意愿使用 Map。
Map.Size; 这会调用我的小函数,它将 aMap 强制转换为 TMap;
Patrick Forest
我之前回答了一个问题,但是经过一些思考和琢磨,我找到了解决循环引用问题的更好方法。这是我的第一个单元,需要在单元B中定义一个指向对象TB的指针。
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.
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.
这里是它为什么能编译的原因。首先,Unit B在实现部分声明了对Unit1的使用。立即解决了Unit1和Unit B之间的循环引用问题。
但是为了让Delphi编译通过,我需要给它提供一些关于FaOwner:TForm1的声明信息。所以,我添加了一个名为TForm1的存根类,它与Unit1中的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来说是未知的。只要TB不需要使用它们或只需要使用TForm提供的共性,您就可以使用它。如果您需要从TB调用特定于UNit1.TForm1的内容,则需要将其强制转换为Unit1.TForm1。
我在Delphi 2010中尝试并测试了它,它编译并正常工作。
希望它能帮助您,减轻一些头痛。
FaOwner: TForm1;
。一旦指针被初始化,它将始终指向同一个对象。这个事实的证明是我可以更改我的代码,忽略B单位中TForm1的声明,并用指针替换它。代码仍然可以运行。但我每次使用它时都需要进行类型转换。但是对于我的存根TForm1,如果我只想使用Unit1.TForm1中声明的东西,则只需要进行类型转换即可。 - Patrick Forest