delphidelphi-units

Acessing several units implementing a Interface with equal names


I have two or more units I need download from third party when your versions have changes.

I use xml databind to generate the units. They are something as:

unit tissV01;

interface

uses .....;

type
  IXMLMensagemTISS = interface(IXMLNode)
    ['{11773827-F0A1-42E0-99E1-E221DFAF8542}']
    { Property Accessors }
  end;


function GetmensagemTISS(Doc: IXMLDocument): IXMLMensagemTISS;

implementation

function GetmensagemTISS(Doc: IXMLDocument): IXMLMensagemTISS;
begin
  Result := XXXX as IXMLMensagemTISS;
end;

end.

Unit tissV02

unit tissV02;

interface

uses .....;

type
  { IXMLMensagemTISS }
  IXMLMensagemTISS = interface(IXMLNode)
    ['{11773827-F0A1-42E0-99E1-E221DFAF8542}']
    { Property Accessors }
    property Cabecalho: string read Get_Cabecalho;
  end;

function GetmensagemTISS(Doc: IXMLDocument): IXMLMensagemTISS;

implementation

function GetmensagemTISS(Doc: IXMLDocument): IXMLMensagemTISS;
begin
  Result := XXXX as IXMLMensagemTISS;
end;

end.

In my app I need to have a choice what unit I have to use:

unit test;

interface

uses tissV01,tissV02, .......;

type 
  TMyform = class(TForm)
  public
    msg3:IXMLMensagemTISS;   
  end;

implementation

procedure TMyform.ExecuteMessage:
var 
  xmlTISS : TXmlDocument;
begin
  xmlTISS := TXmlDocument.Create(nil); 
  if condition  then
    msg3 := tissV01.GetmensagemTISS(xmlTISS)
  else msg3 := tissV02.GetmensagemTISS(xmlTISS);
  with msg3.Cabecalho do  something;
end; 

end.

Logically, it doesn´t work because IXMLMensagemTISS is common to both units.

Is there some workaround to do it without I have to change the name of the Interface names(IXMLMensagemTISS)?

I´d like to simplify my code and I need maintain many units of this type in the future. The problem is that all implement IXMLMensagemTISS and I can do nothing to change it.

I´d not like to create many msg variables such as msgV01:=tissv01.GetmensagemTISS, msgV01:=tissv02.GetmensagemTISS, ... and so on


Solution

  • If you have two identical identifiers in different units you can prefix the unit name to differentiate them.

    var
      a: tissV01.IXMLMensagemTISS;
      b: tissV02.IXMLMensagemTISS;
    

    In your sample code however you need to make an explicit choice which interface to use.

    uses
      tissV01, tissV02;  //last unit in uses clause gets priority.
    
    type 
      TMyform = class(TForm)
      public
        msg3: tissV01.IXMLMensagemTISS;   //allowed
        msg2: tissV02.IXMLMensagemTISS;   //allowed
        msgx: IXMLMensagemTISS; //ambigous, will evaluate to tissV02.IXMLMensagemTISS;
      end;
    

    The last unit in a uses clause gets prioritised.
    This fact is often abused to override built in classes and interfaces with custom ones.

    If you wish to delay the choice based on some condition you can use conditional compilation.
    Either in the uses clause (making use of the priority effect of the uses clause order),

    unit DoWork;
    
    interface
    
    uses
     {$ifdef V01HasPriority}
     tissV02, tissV01;
     {$else}
     tissV01, tissV02;
     {$endif}
    

    or explicitly in the declarations

    var
      a: {$ifdef useV01} tissV01.IInt {$else} tissV02.IInt {$endif}
    

    You then make the choice somewhere else using a {$define V01HasPriority} that compiles prior to the {$ifdef ...}.
    You can also declare the {$define ...} in the IDE.

    Project > Options > Delphi Compiler > Conditional defines.

    You can only choose the interface at runtime if the interfaces are compatible.
    That means the interfaces inherit from a common ancestor.
    Every interface has a common ancestor in IInterface, however it is best to choose an interface as close as possible to both.

    Then you declare a variable as that common ancestor:

    var
      a: ICommonInterface;
    begin
      if x=1 then a:= tissV01.NewXMLInterface
      else a:= tissV02.NewXMLInterface;
      if Supports(a, tissV01.IXMLInt) then tissV01.IXMLInt(a).DoV01Things
      else tissV02.IXMLInt(a).DoV02Things;  
    

    If both interfaces have the same signature then things are much easier (and much saner).

    var
      a: IXMLCommon;
    begin
      if x=1 then a:= tissV01.NewXMLInterface
      else a:= tissV02.NewXMLInterface;
      a.DoCommonThings(param1, param2);
    

    Centralizing the decision making
    Of course if you have lots of decisions to make it's (sometimes) better to centralize them then to spread them all over your program.

    So why not create a unit where all the decision making gets done, like so:

    unit IvoryTower;
    
    interface
    
    function InterfaceXorY(const person: TPerson): ICommonIntf;
    
    implementation
    
    function InterfaceXorY(const person: TPerson): ICommonIntf;
    var
      WhatToDo: TSomething;
    begin
      WhatToDo:= DatabaseY.TableX.GetData(Person);
      case WhatToDo of
        XYZolog: Result:= Unit1.I1;
        Galaga: Result:= Unit2.I2;
        Twinbee: Result:= Unit3.I4;
        else Assert(false, 'what to do outside of valid range');
      end; {case}
    end;