Sooner or later you run into the circular unit reference problem when using a single unit for each class. There are some solutions (move the common used structs into a separate unit, use type casting, redesign your obvious bad class design, etc.) – this is a solution using class helpers. Recommended reading: “Class Helpers – good or bad ?” and “New Delphi language features since Delphi 7“:
“Class helpers provide a way to extend a class, but they should not be viewed as a design tool to be used when developing new code. They should be used solely for their intended purpose, which is language and platform RTL binding. “
Let’s suppose we have a simple TDog class:
unit Dog; type TDog = class Name: String; end; |
and a corresponding TMaster class:
unit Master; type TMaster = class Name: String; end; |
Let’s create our objects:
uses Dog, Master; var Dog: TDog; Master: TMaster; begin Dog := TDog.Create; Dog.Name := 'Bobby'; Master := TMaster.Create; Master.Name := 'Joe'; end; |
Works. Now we’re adding the dog’s master in our TDog class:
unit Dog; uses Master; type TDog = class Name: String; Master: TMaster; end; |
Ok, compiles. Alright. Now what if we need a reference to our dog in the TMaster class? First approach:
unit Master; uses Dog; // Compile error type TMaster = class Name: String; Dog: TDog; end; |
Won’t compile – circular unit reference (unit Dog uses unit Master, and unit Master uses unit Dog). You may getting around by using TObject as the Dog object:
TMaster = class Name: String; Dog: TObject; end; |
Well, that compiles – but you always have to type cast your Dog object:
[..] begin Dog := TDog.Create; Dog.Name := 'Bobby'; Master := TMaster.Create; Master.Name := 'Joe'; Master.Dog := Dog; // The Master has a dog Dog.Master := Master; // The dog's master is Master Writeln(TDog(Master.Dog).Name); end; |
Now – how about using class helpers? First we move the Dog object into our protected area:
unit Master; type TMaster = class protected FDog: TObject; public Name: String; end; |
then we introduce a new class helper which “exports” the dog as a TDog class
unit MasterClassHelper; uses Dog, Master; type TMasterClassHelper = class helper for TMaster private procedure SetDog(Value: TDog); function GetDog: TDog; public property Dog: TDog read GetDog write SetDog; end; function TMasterClassHelper.GetDog: TDog; begin Result := TDog(FDog); // We cast the TObject(FDog) to TDog! end; procedure TMasterClassHelper.SetDog(Value: TDog); begin FDog := Value; end; |
Now we’re able to access the Dog reference of the Master object without typecasting:
uses Dog, Master, MasterClassHelper; var Dog: TDog; Master: TMaster; begin Dog := TDog.Create; Dog.Name := 'Bobby'; Master := TMaster.Create; Master.Name := 'Joe'; Master.Dog := Dog; // The Master has a dog Dog.Master := Master; // The dog's master is Master Writeln(Master.Dog.Name); // "Bobby" Writeln(Dog.Master.Name); // "Joe"; Writeln(Dog.Master.Dog.Master.Dog.Master.Dog.Name); // "Bobby" end; |
Adding the MasterClassHelper in the implementation part of the Master unit allows us to access the TDog class inside our TMaster class, too:
unit Master; type TMaster = class protected FDog: TObject; public Name: String; procedure RenameDog; end; implementation uses MasterClassHelper; procedure TMaster.RenameDog; begin Dog.Name := 'Buddy'; end; |
Download Delphi source DogMaster.zip