Berocoder 16 Posted December 30, 2018 (edited) 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 December 30, 2018 by Berocoder Share this post Link to post
Guest Posted December 30, 2018 (edited) 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 December 30, 2018 by Guest Share this post Link to post
Berocoder 16 Posted December 30, 2018 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
Guest Posted December 30, 2018 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
David Schwartz 427 Posted December 31, 2018 If you have circular references, then you need to re-think your design. Share this post Link to post
Berocoder 16 Posted December 31, 2018 (edited) 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 December 31, 2018 by Berocoder Share this post Link to post
Uwe Raabe 2060 Posted December 31, 2018 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
Berocoder 16 Posted December 31, 2018 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
Uwe Raabe 2060 Posted December 31, 2018 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
Berocoder 16 Posted December 31, 2018 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
Uwe Raabe 2060 Posted December 31, 2018 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
Uwe Raabe 2060 Posted January 1, 2019 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. 1 Share this post Link to post
Berocoder 16 Posted January 1, 2019 (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 January 1, 2019 by Berocoder Share this post Link to post
David Schwartz 427 Posted January 2, 2019 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
Berocoder 16 Posted January 2, 2019 (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 January 2, 2019 by Berocoder Share this post Link to post