有没有办法在Delphi中绕过循环单元引用?
也许是较新版本的delphi或某些魔法黑客还是什么?
我的delphi项目有10万多行代码,主要基于单例类。我需要重构这个,但这意味着几个月的“循环引用”地狱:)
答案 0 :(得分:40)
过去10年来,我一直在维护近百万行遗留代码,所以我理解你的痛苦!
在我维护的代码中,当我遇到循环使用时,我经常发现它们是由单元A中单元A所需的常量或类型定义引起的。(有时候它也是一小段代码单元A中的(甚至是全局变量),单元B也需要。
在这种情况下(当我很幸运时!)我可以仔细地将代码的这些部分提取到一个包含常量,类型定义和共享代码的新单元C.然后单元A和B使用单元C.
我发布上述内容时有些犹豫,因为我不是软件设计方面的专家,并且意识到这里有许多其他人比我更了解情况。但是,希望我的经验会对你有所帮助。
答案 1 :(得分:13)
答案 2 :(得分:9)
尽可能使用实现部分,并将接口使用子句中的内容限制为具有在接口声明中可见。
没有“神奇的黑客”。循环引用会导致编译器无限循环(单元A需要编译单元B,这需要编译单元A,这需要编译单元B等)。
如果您有一个特定的实例,您认为无法避免循环引用,请编辑您的帖子并提供代码;我相信这里有人可以帮助你弄清楚如何修复它。
答案 3 :(得分:7)
有许多方法可以避免循环引用。
委托。 通常,对象将执行一些应该在事件中完成的代码,而不是由对象本身完成。无论是因为项目工程师的工作时间太短(不是我们总是?),没有足够的经验/知识或者只是懒惰,这样的代码最终会在应用程序中出现。真实世界的例子:TCPSocket组件直接更新应用程序的MainForm上的一些可视组件,而不是让主窗体在组件上注册“OnTCPActivity”过程。
抽象类/接口。使用其中任何一个都可以消除许多单元之间的直接依赖关系。抽象类或接口可以在其自己的单元中单独声明,从而将依赖性限制为最大值。例子:我们的应用程序有一个调试表单。它几乎用于整个应用程序,因为它显示来自应用程序各个区域的信息。更糟糕的是,每个允许显示调试表单的表单也最终都需要调试表单中的所有单元。一种更好的方法是使用一个基本上为空的调试表单,但它具有注册“DebugFrames”的能力。
TDebugFrm.RegisterDebugFrame(Frame:TDebugFrame);
这样,TDebugFrm没有自己的依赖(除了在TDebugFrame类上)。需要显示调试表单的任何和所有单元都可以这样做,而不会冒太多依赖项的风险。
还有很多其他例子......我打赌它可以填补自己的一本书。以时间有效的方式设计干净的类层次结构非常困难,并且它具有经验。了解可用于实现它的工具以及如何使用它是实现它的第一步。但要回答你的问题......对你的问题没有一个尺寸适合所有答案,它总是根据具体情况进行。
答案 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函数。一个简单的功能。
很酷,我想。现在,每次我需要调用Map的属性时,我只使用Map.MyProperty。实施
使用地图;
功能图:TMap; 开始 结果:= TMap(TTile.Map); 端;
哎哟!编译了! :)没有按预期的方式工作。编译器使用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测试它并编译和工作。
希望它会有所帮助,让你头疼。