Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I have two units and a class in each unit that need each other to function. How do I avoid a circular reference?

I have 2 classes, ClassA and ClassB, each in their own seperate unit, UnitA and UnitB.

UnitA uses UnitB. This allows ClassA to call the constructor for ClassB. Part of ClassB's functionality however requires the use of ClassA's methods. I cannot make UnitB use UnitA, else this causes a circular reference. What alternative do I have in allowing ClassB to access ClassA's methods from within UnitB?

like image 294
Tomasz Rutkowski Avatar asked Apr 27 '15 22:04

Tomasz Rutkowski


2 Answers

It depends a lot on how the two classes need to use each other. If one class uses the other in the implementation, but not the interface, then you can move that unit in the uses clause from the interface section down to the implementation section instead.

This would work either way around:

UnitA;

interface

uses
  Classes, UnitB;

type
  TClassA = class(TObject)
  end;

...

.

UnitB;

interface

uses
  Classes;

type
  TClassB = class(TObject)
  end;

implementation

uses
  UnitA;

...

However, if the interface of both your classes rely on each other, then you have no choice but to put both classes in the same unit (and using forward declarations).

UnitA;

interface

uses
  Classes;

type
  TClassA = class;
  TClassB = class;


  TClassA = class(TObject)
  end;

  TClassB = class(TObject)
  end;

...

In rare cases, you might even be able to move an entire class definition down to implementation. That wouldn't be useful though if you needed to actually use that class elsewhere.

UnitB;

interface

implementation

uses
  Classes, UnitA;

type
  TClassB = class(TObject)
  end;

...

Depending on the complexity, it's also common practice to put commonly shared code in a separate unit of its own. For example constants, record array types, global variables, etc. This common unit shall not use any other of these units.

like image 75
Jerry Dodge Avatar answered Nov 15 '22 03:11

Jerry Dodge


To expand a little on the previous answer, another possiblity (although definitely less readable) is to refer to an ancestor that both have acess to ( in the extreme case TObject) and then cast in the implementation stage. So

interface

TClassA = class
...
fRefToClassB : TObject;  // internal ref
...
procedure UsingBRef( pVar : TObject );
...
end;

implementation

procedure TClassA.UsingClassB
begin
  with fRefToClass as TClassB do
...
end;

procedure TClassB.UsingBRef( pVar : TObject );
begin
  with pVar as TClassB do
...
end;

Obviously there are many variations on a theme and you would implement is better than this. Again I would emphasise the previous solution is better, but this provides a get out when all else fails.

like image 2
Dsm Avatar answered Nov 15 '22 04:11

Dsm