Tuesday, August 22, 2006

Avoiding circular refrences

Sometimes its the simple things that make it all worth while. As I once again took out my fork and began to separate out a huge pile of spaghetti code that was left for me, I needed to come up with a better method to avoid the circular reference trap. I had several units which contained classes which were directly referencing the main form. The circular reference here was troubling me as if I needed to pull one of the units out for another project, the entire application would go along with it.

My solution turned out to be quite simple. I created a new interface unit which contained nothing more than a set of interfaces:

unit MyProgram_Intf;
interface
// ---------------------------
type
iMainGui = interface
['{3D8EC075-0A0D-438E-9BCD-9BDDE10112E8}']
procedure SetStatus(sStatusMsg:String;iProgress:Integer);
end;

iMyModule = interface
['{A6A09A36-C4F2-4E2E-B231-B51A948FE4D4}']
procedure SetMainGui(pMainGui:iMainGui);
function PerformTask : boolean;
end;
//----------------------------
implementation
end.

After saving this unit and adding it to my main form and my child forms, I then changed each child form unit to something like:

tModuleOne = class(tForm,iMyModule)
private
fMainGui : iMainGui;
procedure SetMainGui(pMainGui:iMainGui);
function PerformTask:boolean;
end;

The implementation of SetMainGui is very simple:

procedure tModuleOne.SetMainGui(pMainGui:iMainGui);
begin
fMainGui := pMainGui;
end;

After adding the implmentation for each of these two methods, I added the following code to the initialization section of the unit (replacing tModuleOne with the name of the form class for this unit):

initialization
register(tModuleOne);
end.

Next I removed each one of the units from the main form to force myself to only use the interface. I implemented the SetStatus routine in the main form. The only thing left was the method to invoke each item. I ended up with the following:

function tMainForm.InvokeTask(sTaskname:String):boolean;
var
fTaskClass : TCustomFormClass;
fTask : tCustomForm;
fModule : iMyModule;
begin
Result := false;
try
fTaskClass := tCustomFormClass(FindClass(sTaskname));
except
// an exception here signals the class was not found
exit;
end;
fTask := fTaskClass.Create(nil);
try
if not Supports(fTask,iMyModule,fModule) then
begin
// if we get here then the module
//doesn't support the correct interface
exit;
end;
fModule.SetMainGui(Self);
result := fModule.PerformTask;
finally
fModule := nil;
fTask.Free;
end;
end;


Then, when I needed to call one of the tasks, I just called the InvokeTask method passing it the name of the class I needed to use. No more direct circular refrences, and each unit could easily be picked up and dropped into another project only requiring the unit with the interfaces is brought along also.

No comments: