gath
gath

Reputation: 25472

Separating Interface and Implementation classes in delphi?

Am separating my delphi code into interface and implementation units ie.

EmployeeIntf.pas looks like this

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;

Then i have the two units ScheduleIntf.pas & DepartmentIntf.pas which declare the TScheduleList class and TDepartment class.

Then in my main unit which combines all the units looks like this,

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.

When i compile this the compiler throws an error;

*Type TScheduleList is not yet completely defined*

How can i have this classes separate in each unit file (.pas) and then do forward declarations without the compiler throwing this error?

The class themselvs are huge and i would prefer to separate them this way.

Upvotes: 2

Views: 5734

Answers (7)

oOo
oOo

Reputation: 291

Here is an improvement, include uses clausule:

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.

The BusinessDomain unit is weird though...

Upvotes: 0

skamradt
skamradt

Reputation: 15538

Include files are one of those legacy pascal features which keep rolling forward. There was a point when we didn't have a uses clause, and it was the only way to manage multiple files (of course we all programmed using wordstar commands for code navigation... wait, they are still there too).

Today, the most common use of include files is to include a block of code which must be shared among multiple files and can't be just used from another unit. As Mason pointed out in another comment, that would be the IFDEF-DEFINE blocks for determining things like what compiler options should be turned on, and what defines should be enabled project wide. We are no longer bound by the 64k limit on a source file.

Some other points to consider. Most of the tools for searching your source may not navigate into your include files. Using something as simple as a text search to find that text message that keeps popping up might be difficult. You would be much better served by not putting any code in them at all. When translating a map file to code, I believe your line numbers specified in the map file would be the total file if the include file was merged in place. If you use an automated tool such as MadExcept the error line reported might not be the actual location.

My suggestion would be to use interfaces as Uwe suggested. They are not just for COM, and can solve your desire to separate the "interface" from the "implementation".

Upvotes: 2

Toon Krijthe
Toon Krijthe

Reputation: 53366

If you really like to separate interface and implementation, have a look at Modula2. This is also a pascal like application, but it uses two files for each "unit". One for the interface and one for the implementation.

Another solution, is to split the files or class definitions, and write a custom preprocessor that links these (text wise) together. You then have something like:

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.

With Delphi 2009 you can issue commands before and after the build phase. So technically this is possible.

Upvotes: 1

Despatcher
Despatcher

Reputation: 1725

Maybe my previous comment was a little presumptuous ... Reading your question again I think you may have misunderstood how to use units and in particular the "uses" directive.

You can declare individual classes both interface and implementation in a single unit file:

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;

Then later to use them elsewehere:

Unit BusinessDomain

interface

uses EmployeeDBCLassesU; // MY units needed by this unit
.
.
.

This brings all the class definition in to BusinessDomain

and you can then do

 TBusinessDomain = class(BDDBobject)
   EmployeeList: TEmployeeList;
   EmployeeDM: TEmployeeDM;
   .
   .
   .;
 end;

Hope this helps more as you will gain so much from the correct approach - you will realise this especially when navigating units for editing and Debugging.

Upvotes: 1

Tim Jarvis
Tim Jarvis

Reputation: 18815

Ulrich is quite correct (+1) whilst you can manipulate your include files to work approx this way, it's not going to be a very good Dev experience for you.

Think of the $Include directive as a simple text replacement mechanism, the things that make your units, well...units is the scoping mechanisms (uses sections, type sections etc) they will be much more difficult to use in an include file, thats why include files are usually called xxx.inc and not xxxx.pas as they often will not stand up as a source file by themselves. This of course makes them very difficult in a dev environment as they are then effectively just text files not proper debuggable units.

Upvotes: 7

Uli Gerhardt
Uli Gerhardt

Reputation: 14001

My first advice: Skip this $Include thing altogether. As Uwe wrote find a more Delphi-like solution.

If you really want to stay with the $Include style: The error you quote occurs because forward declarations don't work across "type" blocks. You forward declare TScheduleList in one block but define it in a different block. To cure this omit the "type" keyword in your *Intf.pas's and insert it in BusinessDomain.pas before the includes.

Upvotes: 15

Uwe Raabe
Uwe Raabe

Reputation: 47704

I'm afraid, there is no possibility to split a class declaration into multiple files. If the classes are that large, you should consider a redesign.

Another alternative are Interfaces:

type
  IEmployee = interface
    { public properties and methods of an employee }
    ...
  end;

type
  TEmployee = class(BDObject, IEmployee)
    ...
  end;

The interface and class declaration can now reside in different files.

Upvotes: 12

Related Questions