Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I avoid repeatedly casting to a descendant interface type?

I'm making a framework (for internal use only) that has common code among 3 o 4 delphi database CRUD applications..

A common object of mi framework is a TContext,

TContext = class (IContext)
  function DB: IDatabase;
  function CurrentSettings: ISettings;
  ..
 end;

that is passed to the initialization method of lots of other objects.. example (this will be application code):

 TCustomer.Initialize(Context: IContext)
 TProjectList.Initialize(Context: IContext)
 ..

Every application has some specific context functions (that only will be called from application code):

 IApp1Context = interface (IContext)
   procedure DoSomethingSpecificToApp1;
   procedure DoOtherThing;
   ..
 end;

So when I create a Context, Im creating a IApp1Context, and sending it to the initialization methods.. from the framework code everything is fine, the problem is that from the application code I'm constantly casting from IContext to IApp1Context to access the specific App1 functions.. so all my application code looks like (and its a lot of code like this):

 (FContext as IApp1Context).DoSomethingSpecificToApp1
 (FContext as IApp1Context).DoOtherThing;
 ..

The thing is clearly usable, but doesnt reads well in my opinion. Maybe I'm exaggerating; is there is a clever design technique for this situation that I'm not aware of?

like image 828
pragmatic_programmer Avatar asked Oct 18 '11 03:10

pragmatic_programmer


3 Answers

Use a temporary variable. Assign it once at the start of the method, and then use it where you need it.

var
  AppContext: IApp1Context;
begin
  AppContext := FContext as IApp1Context;

  AppContext.DoSomethingSpecificToApp1;
  AppContext.DoOtherThing;
end;

Or, since it looks like your IContext object is a field of an object, make your IApp1Context variable be a field of the same object. It could even replace the IContext field since IApp1Context already exposes everything the IContext does.

procedure TCustomer.Initialize(const Context: IContext);
begin
  FContext := Context;
  FAppContext := FContext as IApp1Context;
  // ...
end;
like image 181
Rob Kennedy Avatar answered Dec 04 '22 07:12

Rob Kennedy


What do you think of this possible solution using generics?

pro: no casting necesary down: the generic invades almost every interface and class of the framework, making it more complicated..

// framework //
type
  IContext = interface
    function DB;
    ..
  end;

  TContext = class (TInterfaedObject, IContext)
    function DB;
    ..
  end;

  IBusinessObj<T: IContext> = interface
    procedure Initialize(AContext: T);
  end;

  TBusinessObj<T: IContext> = class (TInterfacedObject, IBusinessObj<T>)
  protected
    FContext: T;
  public
    procedure Initialize(Context: T); virtual;
  end;

procedure TBusnessObj<T>.Initialize(Context: T);
begin
  FContext := Context;

  FContext.DB.Connect;
end;

// application //
type
  IApp1Context = interface (IContext)
    procedure DoSomethingElse;
    ..
  end;

  TApp1Context = class(TContext, IContext, IApp1Context)
    function DB;
    procedure DoSomethingElse;
  end;

  TCustomer = class(TBusinessObj<IApp1Context>)
  public
    procedure Initialize(AContext: IApp1Context); override;
  end;

procedure Start;
var
  C: IBusinessObj<IApp1Context>;
begin
  C := TCustomer.Create;
  C.Initializate(TApp1Context.Create);
  ..
end;

procedure TCustomer.Initialize(AContext: IApp1Context);
begin
  inherited;

  FContext.DoSomethingElse // here I can use FContext as a IApp1Context.. 
end;

Comment please, Thanks!

like image 29
pragmatic_programmer Avatar answered Dec 04 '22 09:12

pragmatic_programmer


You could also give your class a private function AppContext defined like this:

function AppContext : IApp1Context;

begin
Result := FContext as IApp1Context;
end;

This avoids the additional variable declaration and keeps the cast local. From client code you can just write:

AppContext.DoSomethingSpecificToApp1;
AppContext.DoOtherThing
like image 42
jpfollenius Avatar answered Dec 04 '22 08:12

jpfollenius