我将我的 delphi 代码分成接口和实现单元,即。
EmployeeIntf.pas 看起来像这样
type
// forward declaration
TScheduleList = class;
TDeparment = class;
TEmployee = class(BDObject)
....
function GetSchedules: TScheduleList;
function GetDepartment: TDepartment;
end;
TEmployeeList = class(DBList)
....
end;
TEmployeeDM = class(BDDBobject)
...
end;
然后我有两个单元ScheduleIntf.pas和DepartmentIntf.pas,它们声明了TScheduleList类和TDepartment类。
然后在我的主单元中,组合了所有单元,如下所示,
Unit BusinessDomain
Interface
uses
classes
{$I Interface\EmployeeIntf.pas}
{$I Interface\DepartmentIntf.pas}
{$I Interface\ScheduleIntf.pas}
Implementation
uses
SysUtils
{$I Implementation\EmployeeImpl.pas}
{$I Implementation\DepartmentImpl.pas}
{$I Implementation\ScheduleImpl.pas}
Initialization
finalization
end.
当我编译它时,编译器会抛出错误;
*Type TScheduleList is not yet completely defined*
如何在每个单元文件(.pas)中将这些类分开,然后进行前向声明而不让编译器抛出此错误?
班级本身很大,我更喜欢这样将它们分开。
加斯
我的第一个建议:完全跳过这个 $Include 的事情。正如 Uwe 所写,找到一个更像 Delphi 的解决方案。
如果您真的想要保留 $Ininclude 样式:您引用的错误会发生,因为前向声明不能跨“type”块工作。您可以在一个块中转发声明 TScheduleList,但在不同的块中定义它。要解决此问题,请忽略 *Intf.pas 中的“type”关键字,并将其插入 BusinessDomain.pas 中的包含之前。
恐怕不可能将一个类声明拆分为多个文件。如果类那么大,你应该考虑重新设计。
另一种选择是接口:
type
IEmployee = interface
{ public properties and methods of an employee }
...
end;
type
TEmployee = class(BDObject, IEmployee)
...
end;
接口和类声明现在可以驻留在不同的文件中。
Ulrich 是非常正确的(+1),虽然你可以操纵你的包含文件以这种方式工作,但这对你来说不会是一个很好的开发体验。
将 $Include 指令视为一个简单的文本替换机制,构成您的单位的东西,嗯...单位是范围机制(使用部分,类型部分等),它们在包含文件中使用起来要困难得多,这就是为什么包含文件通常被称为 xxx.inc 而不是 xxxx.pas,因为它们本身通常不能作为源文件。这当然使它们在开发环境中变得非常困难,因为它们实际上只是文本文件而不是适当的可调试单元。
包含文件是不断向前发展的传统 pascal 功能之一。曾经有一段时间,我们没有 use 子句,而这是管理多个文件的唯一方法(当然,我们都使用 wordstar 命令进行编程以进行代码导航......等等,它们也还在那里)。
如今,包含文件最常见的用途是包含必须在多个文件之间共享的代码块,并且不能仅从另一个单元使用。正如 Mason 在另一条评论中指出的那样,这将是 IFDEF-DEFINE 块,用于确定应打开哪些编译器选项以及应在项目范围内启用哪些定义等内容。我们不再受源文件 64k 限制的束缚。
其他一些需要考虑的要点。大多数用于搜索源代码的工具可能无法导航到包含文件。使用文本搜索这样简单的方法来查找不断弹出的文本消息可能很困难。如果根本不将任何代码放入其中,您会得到更好的服务。将映射文件转换为代码时,我相信如果包含文件已就地合并,则映射文件中指定的行号将是整个文件。如果您使用 MadExcept 等自动化工具,报告的错误行可能不是实际位置。
我的建议是使用 Uwe 建议的接口。它们不仅仅适用于COM,并且可以解决您将“接口”与“实现”分离的愿望。
也许我之前的评论有点自以为是......再次阅读你的问题,我认为你可能误解了如何使用单位,特别是“uses”指令。
您可以在单个单元文件中声明各个类的接口和实现:
unit EmployeeDBCLassesU
uses system, DB, Blah, blah; // Units needed by this unit
interface
type
TEmployeeList = class(DBList)
Procedure DoSomething;
end;
TEmployeeDM = class(BDDBobject)
Procedure DoSomething;
end;
implementation
{TEmployeeList}
Procedure TEmployeeList.DoSomething;
begin
...
end;
{TEmployeeDM }
Procedure TEmployeeDM.DoSomething;
begin
...
end;
然后在其他地方使用它们:
Unit BusinessDomain
interface
uses EmployeeDBCLassesU; // MY units needed by this unit
.
.
.
这会将所有类定义带入 BusinessDomain 中
然后你就可以做
TBusinessDomain = class(BDDBobject)
EmployeeList: TEmployeeList;
EmployeeDM: TEmployeeDM;
.
.
.;
end;
希望这对您有更多帮助,因为您将从正确的方法中获益匪浅 - 您会意识到这一点,尤其是在导航单元进行编辑和调试时。
如果你真的喜欢将接口和实现分开,请看看Modula2。这也是一个类似 pascal 的应用程序,但每个“单元”使用两个文件。一份用于接口,一份用于实现。
另一个解决方案是拆分文件或类定义,并编写一个自定义预处理器将它们(文本方式)链接在一起。然后你会得到类似的东西:
unit BusinessDomain
interface
uses
classes;
type
Employment = class from Interface\EmployeeIntf.pas;
Department = class from Interface\DepartmentIntf.pas;
Schedule = class from Interface\ScheduleIntf.pas;
implementation
uses
SysUtils;
external define
Employment = class from Implementation\EmployeeImpl.pas;
Department = class from Implementation\DepartmentImpl.pas;
Schedule = class from Implementation\ScheduleImpl.pas;
end;
end.
使用 Delphi 2009,您可以在构建阶段之前和之后发出命令。所以从技术上来说这是可能的。
这是一个改进,包括使用条款:
unit EmployeeIntf;
interface
uses
DepartmentIntf,
ScheduleIntf;
//type
// // forward declaration
// TScheduleList = class;
// TDeparment = class;
type
TEmployee = class
// ....
function GetSchedules: TScheduleList;
function GetDepartment: TDepartment;
end;
TEmployeeList = class
// ....
end;
TEmployeeDM = class
// ...
end;
implementation
function TEmployee.GetSchedules: TScheduleList;
begin
end;
function TEmployee.GetDepartment: TDepartment;
begin
end;
end.
BusinessDomain 单元很奇怪...