tags:

views:

691

answers:

6

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.

Gath

+10  A: 

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.

Uwe Raabe
+1 Just what I was about to suggest.
skamradt
+13  A: 

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.

Ulrich Gerhardt
It worked on your second advice, removing the type keyword from the *Intf.pas's. Thanks.
gath
I know you have a solution that "works" for this layout/style but I'd be prepared to lay money that you will change it back to the true Delphi way later on :) You lose so much ease of code automation and navigation. Tim
Despatcher
Agreed. See Tim's answer.
Ulrich Gerhardt
+7  A: 

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.

Tim Jarvis
The separation actually makes the whole application feel easy to navigate, Business objects not only are they logically separated via classes, but also physically by unit separation, easy too locate them and naviagte.
gath
Lets have this conversation again in 6 months :-)
Tim Jarvis
+1  A: 

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.

Despatcher
A: 

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.

Gamecat
What would be the advantage of the custom preprocessor over the $Include solution?
Ulrich Gerhardt
I think it is a matter of taste. Include files do not really follow the delphi spirit. With a preprocessor, you can more or less extend the language.
Gamecat
Agreed. Include files are great for setting up IFDEF-DEFINE blocks for configuration purposes, but if you have any actual code in there, you're usually doing something wrong.
Mason Wheeler
@Mason Wheeler: Two words "legacy code". We are on a war against IFDEF,s but its not an easy one.
Gamecat
What's wrong with IFDEFs? They're often the only way to keep things compatible with multiple versions of Delphi. (Or to have the same unit work with two different programs that have *slightly* different requirements.)
Mason Wheeler
Nothing wrong if there are small differences, but big differences are a pain in the ...
Gamecat
+2  A: 

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".

skamradt
I use that WordStar-like key combinations till now !! :-)
Fabricio Araujo
Am reverting to delphi like way.
gath
Good man, Stackoverflow rules OK!
Despatcher