Quantcast
Channel: Delphi Programming – The curse of Dennis D. Spreen
Viewing all articles
Browse latest Browse all 19

Solving circular unit references with class helpers

$
0
0

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


Viewing all articles
Browse latest Browse all 19

Latest Images

Trending Articles



Latest Images