类之间存在循环引用

4

我需要开发以下的图表类: enter image description here
我写了代码,但是遇到了循环单元引用的问题。

XmlFileManager类包含:

unit XmlFileManager;
interface
uses
  xmldom, XMLIntf, msxmldom, XMLDoc, SysUtils, DateUtils, Classes, Dialogs,
  XmlEnpManager;
type
  TXmlFileManager = class
  private
    [...]
    xmEnp: TXmlEnpManager;
    xmEnpInicial: TXmlEnpManager;
    xmEnpFinal: TXmlEnpManager;
[...]
end.

抽象类 XmlNodeManager:
unit XmlNodeManager;
interface
uses
  xmldom, XMLIntf, msxmldom, XMLDoc, SysUtils, DateUtils, Classes, Dialogs,
  XmlFileManager;
type
  TXmlNodeManager = class
   protected
        { sgy alias para strategy }
        sgyIterator: Integer;
        sgyContext: TXmlFileManager;
        sgyAttributes: TStringList;
        sgyNode: IXMLNode;
[...]
end.

XmlEnpManager具体类:

unit XmlEnpManager;
interface
uses
  xmldom, XMLIntf, msxmldom, XMLDoc, SysUtils, DateUtils, Classes, Dialogs,
  XmlNodeManager;
type
  TXmlEnpManager = class (TXmlNodeManager)
    public
        constructor Create(aContext: TXmlFileManager); overload; override;
        constructor CreateInicial(aContext: TXmlFileManager); reintroduce; overload;
        constructor CreateFinal(aContext: TXmlFileManager); reintroduce; overload;
[...]
end.

构建失败并出现以下错误:

[dcc32 致命错误] XmlNodeManager.pas(7): F2047 对 'XmlFileManager' 的循环单元引用

有什么解决办法吗?

9
这真是一件非常烦人的事情。熟悉其他编程语言的程序员会想知道我们在说什么。为什么这是一个问题呢?循环引用必然导致你将所有的代码放在同一个单元中。你是否曾经想过为什么 Delphi 的源文件往往如此之大?看看一些 VCL 单元。完全就是庞然大物。这在很大程度上是为什么 Delphi 可执行文件如此巨大的原因。最近的 RTTI 更改使情况变得更糟。当然,您可以使用接口来打破依赖关系。但那就像是经典的本末倒置。太不好了。 - David Heffernan
5个回答

5
TXmlFileManagerTXmlNodeManager 放置于同一个 unittype 段落中,并确保 type 段落以此类前向声明开始:TXmlNodeManager = class; 请参考官方文档:前向声明和相互依赖的类
unit XmlFileManagerAndXmlNodeManager;
interface
uses
  xmldom, XMLIntf, msxmldom, XMLDoc, SysUtils, DateUtils, Classes, Dialogs,
[...]

type
  TXmlNodeManager = class;

  TXmlFileManager = class
  private
    [...]
    xmEnp: TXmlEnpManager;
    xmEnpInicial: TXmlEnpManager;
    xmEnpFinal: TXmlEnpManager;
[...]

  TXmlNodeManager = class
   protected
        sgyIterator: Integer;
        sgyContext: TXmlFileManager;
        sgyAttributes: TStringList;
        sgyNode: IXMLNode;
[...]
end.

1
如果可能的话,最简单的方法是将所有类放在同一个单元中。

可能性是存在的。取决于您的编码标准,如果您想使用它 :) - Harriv

1
尽管将所有类放在同一个单元中是一种完美的解决方案,但您可能会考虑通过在至少一个类上进行接口来打破相互引用,将该接口定义在单独的单元中,并让其他单元在其接口部分引用具有接口的单元。然后,您可以将第一个单元引用从接口移动到实现中。创建实例将在实现部分完成,在该部分中允许相互引用,但这样会打破接口部分中不允许的相互引用,正如您已经注意到的那样。这使您可以将两个类保留在单独的单元中,如果需要的话。希望这有意义。

0
如果单元之间的引用在实现部分而不是接口部分,则可以使用此方法。您可以在另一个单元中创建一个基类,没有外部引用,并从接口引用它,在需要时通过将特定单元引用添加到实现部分来转换为实际类。

不会,因为代码中存在依赖关系。其中两个类必须在同一个单元中。 - Ken White
或者它们必须是第三个单元中一个类的后代。 - mg30rg

0
在XMLNodeManager中,进行如下操作:

sgyContext: TObject;

然后你就不需要在接口中使用XmlFileManager单元了。在实现部分的Uses子句中使用它。在实现代码中使用sgyContext时,将其转换为TXmlFileManager(sgyContext)。

3
这是一个糟糕的想法。这意味着你需要在各个地方使用丑陋的类型转换,而编译器无法捕获任何错误(你可能会通过错误传递一个 TButton,它将允许并尝试将按钮强制转换为 TXMLFileManager - 它可以编译和运行,但几乎肯定会在运行时崩溃)。 - Ken White
sgyContext不是经常通过参数传递的东西。它是一个成员,可以在构造函数或实用程序函数中设置该成员,在设置之前可以检查类类型的代码。一旦正确设置,就不会出现错误。有时候,当你必须有必须分开的单位时,这种技术可能有效。我知道从“纯粹主义”的角度来看,这是不行的。但我们正在寻找所有可能的解决方案,这是其中一种解决方案。 - user1917584
不是的。 :-) 你为了实现一个糟糕的想法而努力工作,比起一开始就正确地做好它,你现在不得不在各个地方进行类型转换,你现在添加了代码来防止在运行时意外传递错误的类型,而正确地做则允许编译器首先检查并防止错误发生。糟糕的代码堆叠在糟糕的代码之上并不能使代码更好。这不是一个解决方案 - 它是臭气熏天的代码堆积在臭气熏天的代码之上,以实现一个糟糕的解决方案。 - Ken White
澄清一下:这相当于决定通过将基类作为参数接受到方法中来实现通用类型解决方案的编码器,并且然后在代码中散布着 if MyParam is TClassA then...else if MyParam is TClassB then...else if...,这是一个糟糕的实现。接受通用的 TObject 作为参数,然后不得不说 if not (MyParam is TheSpecifTypeItShouldHaveBeen) then 来解决它,这只是一个糟糕的实现。 - Ken White

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接