Jump to content

Recommended Posts

I found http://docwiki.embarcadero.com/RADStudio/XE2/en/Classes_and_Objects#Forward_Declarations_and_Mutually_Dependent_Classes

 

I try to create 2 classes. Each of those have a reference to the other.


 

unit TestA;

interface

uses
  TestB;

type
//  TTestB = class;

  TTestA = class
  public
    M_TestB: TTestB;
  end;

implementation

end.
unit TestB;

interface

uses
  TestA;

type
  TTestB = class
    M_TestA: TTestA;
  end;
  
implementation

end.

Got

[DCC Error] TestA.pas(6): F2047 Circular unit reference to 'TestA'

 

What is wrong here ?

 

If I remove TestB from uses and add

 

TTestB = class;

 

I got this instead

 

[DCC Error] TestA.pas(9): E2086 Type 'TTestB' is not yet completely defined

 

 

 

Edited by Berocoder

Share this post


Link to post

The scope of a forward declaration is the type declaration section (typically the unit, or the enclosing type declaration section of a nested class). If the classes directly reference each other you'll have to put them in the same declaration section (unit or enclosing type).

There are workarounds, depending on what you need. For example, you could reference a generic ancestor (e.g. TTestBase, or even TObject) in the interface section, and typecast to the actual class in implementation, or use polymorphism. Circular unit references in the implementation section are allowed.

Edited by Ondrej Kelle

Share this post


Link to post

Ok so basically it is not possible to have direct references in both directions between two classes if they are stored in separate units...

Share this post


Link to post
5 minutes ago, Berocoder said:

Ok so basically it is not possible to have direct references in both directions between two classes if they are stored in separate units...

That's correct.

Share this post


Link to post
18 hours ago, Ondrej Kelle said:

That's correct.

 

2 hours ago, David Schwartz said:

If you have circular references, then you need to re-think your design.

See https://stackoverflow.com/questions/53980032/how-to-avoid-using-inc-files-for-code-with-delphi for more details. 

 

But I now believe that interfaces is the correct solutions instead of direct references between the classes.

Edited by Berocoder

Share this post


Link to post

Besides Interfaces there are also abstract classes to accomplish this.

unit TestA;

interface

type
  TAbstractTestB = class
  public
    procedure Test; virtual; abstract;
  end;

  TTestA = class
  public
    M_TestB: TAbstractTestB;
    constructor Create;
  end;

implementation

uses
  TestB;
  
constructor TTestA.Create;
begin
  inherited Create;
  M_TestB := TTestB.Create;
end.
unit TestB;

interface

uses
  TestA;

type
  TTestB = class (TAbstractTestB)
    M_TestA: TTestA;
  public
    procedure Test; override;
  end;
  
implementation

procedure TTestB.Test;
begin
end;

end.

Depending on where M_TestB is assigned, you assign either a TTestB instance or create one. You can even create that instance inside TTestA as you are allowed to use unit TestB in the implementation section of TestA as shown above. 

 

Nevertheless I suggest to remove these cyclic class dependencies altogether, but the way to do so depends heavily on your use case and the framework you are using.

Share this post


Link to post

The code I posted here is heavily simplified. I agree that cyclic dependencies is not good. 

But basically one class is one table in database for most cases. There is also some transient classes that reside only in memory.

So this is about relations between classes.

Many times I want to navigate both from class A to B and from B to A.

For example class TPerson have a relation to TAddress.
So to find the address:

 

  vAddress := aPerson.Address,AsString:;

 

But to find the person who live at an address make also sense

 

  vName := aAddress.Person.AsString;

 

So one way to break dependency is to use interface. But there are many questions left to work with as the whole framework now assume direct access.

So I want to work with IAddress and IPerson instead of TAddress and TPerson.

But at some point I must deal with the concrete class anyway.

 

About abstract classes, that is a good point. But what happens with the inheritance chain.

 

TObject
TBoldMemoryManagedObject   // inherited in framework

TBoldFlaggedObject                   // inherited in framework

TBoldSubscribableObject           // inherited in framework
TBoldElement                              // inherited in framework
TBoldDomainElement                 // inherited in framework
TBoldMember                             // inherited in framework
TBoldObject                                // inherited in framework
TBusinessClassesRoot                // Dummy
TAmObject                                 // basic info   
TAmStateObject                         // have states  

TPerson                                      // concrete class 

This is the inheritance tree for TPerson. Is it even possible to use a TAbstractPerson somewhere ?

Share this post


Link to post
1 hour ago, Berocoder said:

This is the inheritance tree for TPerson. Is it even possible to use a TAbstractPerson somewhere ?

What about TPerson -> TAbstractPerson -> TAmStateObject ?

Share this post


Link to post
9 hours ago, Uwe Raabe said:

What about TPerson -> TAbstractPerson -> TAmStateObject ?

Btw, that means I have to typecast every access  to the concrete class ?

 

Share this post


Link to post
1 hour ago, Berocoder said:

Btw, that means I have to typecast every access  to the concrete class ?

No. you have to copy the methods from TPerson as abstract methods to TAbstractPerson. Any properties go to TAbstractPerson with abstract Getters and Setters.

Share this post


Link to post

Basically it is like declaring the class interface where it is needed and move the implementation to another unit. Very similar to interfaces - just another approach.

  • Like 1

Share this post


Link to post
Posted (edited)

I investigated more now and unfortunately I don't get this as I want
Declaration of TestAbstractB happen in unit TestA. I want to move it to unit TestB as there can be many classes that have references to that class.

But then again I got 

[DCC Error] TestA.pas(6): F2047 Circular unit reference to 'TestA'

 

as TestB have a reference to TestA.

So I don't know. Using interfaces need huge changes in framework and insert Abstract classes don't work as I expected.

So maybe we continue with original implementation with inc-files 🙂

 

[EDIT]

Simple solution to that was to store abstract declarations in own unit.

So far so good 🙂

 

 

Edited by Berocoder

Share this post


Link to post

As I said earlier, if you have circular references, you need to rethink your approach.

 

If a "person" can have an "address" as a property, and an "address" can have a "person" as a property, you've got a problem.

 

If you think of this in normal "in real life" terms, sure, a "person" DOES have an "address". But more abstractly, you can say, "person has a residence address"

 

And, a "person" could have a "work address", and a "previous residence/work address" and so forth.

 

Now, an "address" usually does not "have a person" -- it could have MANY such "persons" associated with it who have different roles: owner, landlord, manager, tenant, maintenance people, etc.

 

So if you look at this more realistically, your simplified explanations may be misleading you in your architecture.

 

Also, you seem to be bumping up against the general notions of "inheritance" vs. "composition", or "Is-A" vs. "Has-A".

 

As Einstein once said, you cannot solve a problem at the same level of logic that caused it.

Share this post


Link to post
Posted (edited)

This is just an experiment, a proof of concept.

The real model have about 460 classes. 

The relations between them can be

- One to one

- One to many

- Many to many. In this case a new association class  is used. So in reality this is two relations. Many to one association class. one (from association class ) to many.

 

In some cases the relation is navigable only in one direction. In some cases in both. In some cases a class have a relation to itself.

 

That's the situation now and it works. I just want to remove usage of inc-files for storing code to make development easier.
Disadvantages with inc-files
- Codeinsight in IDE don't work

- Many tools to analyze source don't handle inc-files well.

- IDE (bds.exe) tends to lock inc-files sometimes, This is random. So restart is required before project can be compiled.

 

In spite of that we used the current solutions in fifteen years.

Edited by Berocoder

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×