Additional
dbExpress, optimization techniques and OOD patterns
Posted by Diman [Apr 09, 2007]
The optimization of code with many branches may be awfull process. But there are the ways to make things optimal, simple and having nice OO design. TDBX - the driver framework of dbExpress 4 - is the good example.

Recently I made serious review (internal) of new version of CodeGear DBX framework. In Delphi 2007 was released DBX 4, where the key role plays the TDBX framework. I may be will write additional blog about it - Delphi Database Driver framework. But right now I am about general optimization approaches and structural OOD patterns.

 

At a code optimization we often trying to reduce the number of commands performing the task, rewriting critical places using ASM, adding IFDEF's to turn off not constantly needed code places, etc. But one approach, used in TDBX is not really coder but architecter approach. And it is simple, effective and true OOD/OOP.

 

Let say, we need to add tracing entries to the start and to the end of methods. That may be coded this way:

 

function TMyObj.Func1(const AArg: String): Integer; 
begin
  FTracer.Put('Func1(AArg = ''' + AArg + ''') enter');
  ...............................
  FTracer.Put('Func1 exit. Result = ' + IntToStr(Result));
end;

 

If the Func1 is called to often and we not always need tracing, then, as first approach, we can add IFDEF's around tracing calls:

 

function TMyObj.Func1(const AArg: String): Integer; 
begin
{$IFDEF DEBUG} 
  FTracer.Put('Func1(AArg = ''' + AArg + ''') enter');
{$ENDIF}
  ...............................
{$IFDEF DEBUG} 
  FTracer.Put('Func1 exit. Result = ' + IntToStr(Result));
{$ENDIF}
end;

 

In that case, if DEBUG is not defined, then there will be no overhead for tracer calls. And believe, in some scenarious that may give around 2 times performance gain or more. But if code is compiled, then there is no ability to turn tracing on. So, either - fast, either - debugging output. Does the middle exists ? Yes:

 

type
  TMyObj = class (TObject)
  public
    function Func1(const AArg: String): Integer; virtual;
  end;

  TMyObjWithTrace = class (TMyObj)
  public
    function Func1(const AArg: String): Integer; override;
  end;

function TMyObj.Func1(const AArg: String): Integer; 
begin
  ...............................
end;

function TMyObjWithTrace.Func1(const AArg: String): Integer; 
begin
  FTracer.Put('Func1(AArg = ''' + AArg + ''') enter');
  Result := inherited Func1(AArg);
  FTracer.Put('Func1 exit. Result = ' + IntToStr(Result));
end;

 

And using class factory pattern, we can create either TMyObj, either TMyObjWithTrace objects. And there will be no overhead for tracing, if we will create just TMyObj. But what, if we going to subclass TMyObj into many functional subclasses ? Then let just use Decorator pattern and get not the middle, but right design:

 

type
  TMyObj = class (TObject)
  public
    function Func1(const AArg: String): Integer; virtual;
  end;

  TMyObjTracer = class (TMyObj)
  private
    FMyObj: TMyObj;
  public
    constructor Create(AMyObj: TMyObj);
    function Func1(const AArg: String): Integer; override;
  end;

function TMyObj.Func1(const AArg: String): Integer; 
begin
  ...............................
end;

constructor TMyObjTracer.Create(AMyObj: TMyObj);
begin
  inherited Create;
  FMyObj := AMyObj;
end;

function TMyObjTracer.Func1(const AArg: String): Integer; 
begin
  FTracer.Put(FMyObj.ClassName + '.Func1(AArg = ''' + AArg + ''') enter');
  Result := FMyObj.Func1(AArg);
  FTracer.Put(FMyObj.ClassName + '.Func1 exit. Result = ' + IntToStr(Result));
end;

 

And there we will get - flexibility, speed and readability. That is almost exact way, how in TDBX is implemented so called "delegate" driver. And tracing and pooling drivers are the first "birds", who are inherited from "delegate" driver. I think, there may be implemented much more nice ideas, like a:

  • data types mapping driver;
  • dataset caching driver;
  • data access profiling driver;
  • etc.

 

Add Comments