【发布时间】:2010-04-15 12:00:21
【问题描述】:
有没有办法绕过 Delphi 中的循环单元引用?
也许是更新版本的 delphi 或一些魔术技巧或其他什么?
我的 delphi 项目有 100 000 多行代码,主要基于单例类。我需要重构它,但这意味着几个月的“循环引用”地狱:)
【问题讨论】:
有没有办法绕过 Delphi 中的循环单元引用?
也许是更新版本的 delphi 或一些魔术技巧或其他什么?
我的 delphi 项目有 100 000 多行代码,主要基于单例类。我需要重构它,但这意味着几个月的“循环引用”地狱:)
【问题讨论】:
在过去的 10 年里,我一直在维护近 100 万行遗留代码,所以我理解您的痛苦!
在我维护的代码中,当我遇到循环使用时,我经常发现它们是由单元B需要的单元A中的常量或类型定义引起的。(有时也是一小段代码(甚至是全局变量)在单元 A 中也是单元 B 需要的。
在这种情况下(如果我很幸运的话!)我可以小心地将这些代码部分提取到一个包含常量、类型定义和共享代码的新单元 C 中。然后单元 A 和 B 使用单元 C。
我有些犹豫地发布了上述内容,因为我不是软件设计方面的专家,并且意识到这里还有很多其他人比我知识渊博。不过,希望我的经验对您有所帮助。
【讨论】:
【讨论】:
尽可能使用实现部分使用,并将接口使用子句中的内容限制为必须在接口声明中可见。
没有“魔法黑客”。循环引用会导致编译器死循环(单元A需要编译单元B,需要编译单元A,需要编译单元B等)。
如果您认为无法避免循环引用的特定情况,请编辑您的帖子并提供代码;我相信这里有人可以帮助您弄清楚如何修复它。
【讨论】:
interface 和 implementation 使用子句以便解决它们)_ .
有很多方法可以避免循环引用。
代表。 很多时候,一个对象会执行一些应该在事件中完成的代码,而不是由对象本身完成。无论是因为从事该项目的程序员的时间太短(我们不是总是这样吗?),没有足够的经验/知识还是只是懒惰,这样的一些代码最终会出现在应用程序中。真实示例:TCPSocket 组件直接更新应用程序 MainForm 上的某些可视化组件,而不是让主窗体在组件上注册“OnTCPActivity”过程。
抽象类/接口。使用它们中的任何一个都可以消除许多单元之间的直接依赖关系。抽象类或接口可以在其自己的单元中单独声明,从而最大限度地限制依赖关系。示例:我们的应用程序有一个调试表单。它几乎用于整个应用程序,因为它显示来自应用程序各个区域的信息。更糟糕的是,每个允许显示调试表单的表单最终也将需要调试表单中的所有单元。更好的方法是使用一个基本上为空的调试表单,但它可以注册“DebugFrames”。
TDebugFrm.RegisterDebugFrame(Frame : TDebugFrame);
这样,TDebugFrm 没有自己的依赖项(除了 TDebugFrame 类)。任何需要显示调试表单的单元都可以这样做,而不会冒险添加太多依赖项。
还有很多其他的例子……我敢打赌它可以写一本书。以高效的方式设计一个干净的类层次结构非常困难,而且需要经验。了解实现它的可用工具以及如何使用它们是实现它的第一步。但是要回答您的问题...您的问题没有万能的答案,始终需要根据具体情况来回答。
【讨论】:
类似问题:Delphi Enterprise: how can I apply the Visitor Pattern without circular references?
Uwe Raabe 提出的解决方案使用接口来解决循环依赖。
【讨论】:
Modelmaker Code Explorer 有一个非常好的向导,可以列出所有用途,包括周期。
它要求你的项目编译。
我同意其他海报的观点,即这是一个设计问题。
您应该仔细查看您的设计,并删除未使用的单元。
在 DelphiLive'09,我做了一个名为 Smarter code with Databases and data aware controls 的会议,其中包含了很多关于良好设计的技巧(不限于 DB 应用程序)。
--杰罗恩
【讨论】:
我找到了一个不需要使用接口但可能无法解决循环引用的所有问题的解决方案。
我在两个单元中有两个课程:TMap 和 TTile。
TMap 包含一个地图并使用等距图块 (TTile) 显示它。
我想在 TTile 中有一个指针指向地图上。 Map 是 TTile 的类属性。
类 Var FoMap: TObject;
通常,您需要在另一个单元中声明每个对应单元...并获取循环引用。
在这里,我如何解决它。
在 TTile 中,我将 map 声明为 TObject,并在 Implementation 部分的 Uses 子句中移动 Map 单元。
这样我可以使用 map 但每次都需要将其转换为 TMap 以访问其属性。
我可以做得更好吗?如果我可以使用 getter 函数进行类型转换。但我需要将 Uses Map 移到 Interface 部分......所以,回到第一格。
在实现部分,我确实声明了一个不属于我的类的 getter 函数。一个简单的函数。
实施
使用地图;
功能图:TMap; 开始 结果 := TMap(TTile.Map); 结束;
很酷,我想。现在,每次我需要调用 Map 的属性时,我只需要使用 Map.MyProperty。
哎哟!编译好了! :) 没有按预期的方式工作。编译器使用 TTile 的 Map 属性,而不是我的函数。
所以,我将我的函数重命名为 aMap。但我的缪斯女神对我说话。不!将 Class 属性重命名为 aMap...现在我可以按照我的意图使用 Map。
地图.大小;这调用了我的小函数,他将 aMap 类型转换为 TMap;
帕特里克·森林
【讨论】:
我之前给出了一个答案,但经过一番思考和摸索后,我找到了解决循环引用问题的更好方法。这里我的第一个单元需要在单元 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.
这里是单元 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.
这里是编译的原因。首先 Unit 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 未知的组件和方法。 Has long TB不需要使用它们或者只需要使用TForm给出的通用性就可以了。如果您需要从 TB 调用 UNit1.TForm1 的特定内容,则需要将其类型转换为 Unit1.TForm1。
我尝试了它并使用 Delphi 2010 对其进行了测试,它编译并运行。
希望对您有所帮助,免得您头疼。
【讨论】:
TSomeEntirelyDifferentForm 的实例创建TB,从长远来看,这可能会导致极端灾难。 (至少这个可以修复)
FaOwner: TForm1; 只是声明一个指向对象的指针。一旦指针被初始化,它将始终指向同一个对象。证明这一点我可以更改我的代码,忽略 B 单元中 TForm1 的声明并用指针替换它。和代码立场。但是我每次使用它时都需要对它进行类型转换。但是对于我的存根 TForm1,如果我想使用仅在 Unit1.TForm1 中声明的东西,我只需要进行类型转换。