IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Delphi Discussion :

Retour d'expérience avec les interfaces.


Sujet :

Langage Delphi

  1. #1
    Membre éclairé Avatar de Kaféine
    Homme Profil pro
    Inscrit en
    Avril 2007
    Messages
    569
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 569
    Points : 736
    Points
    736
    Par défaut Retour d'expérience avec les interfaces.
    Bonjour,

    J'ai eu un souci que je voudrais partager avec vous mon retour d'experience. Je m'explique :
    J'ai implémenté un EventMulticast avec Delphi et je me suis heurté à un problème.

    comment connaitre la classe de l'objet implementant une interface
    quand on a seulement une référence de cette interface?

    Exemple: Test est du type ITest;

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
      if Test is TEventMulticaster then
        (...)
    Ma première idée était de déclarer une interface IImplementeurProvider comme suit

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
      IImplementorProvider = interface [GUID]
        function GetImplementeur: TObject;
      end;
    GetImplementeur se contente de renvoyer Self.
    en faisant dériver ITest de IImplementeurProvider on a

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
      if Test.GetImplementeur is TEventMulticaster then
        (...)
    Cette technique fonctionne bien.

    et puis je suis tombé sur cet article
    http://hallvards.blogspot.com/2004/0...in-delphi.html

    Je reporte le code ici au cas où le lien décéderais.
    j'ai juste eu besoin de remplacer PChar par PAnsiChar pour que ca fonctionne.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    function GetImplementingObject(const I: IInterface): TObject;
    const
      AddByte = $04244483;
      AddLong = $04244481;
    type
      PAdjustSelfThunk = ^TAdjustSelfThunk;
      TAdjustSelfThunk = packed record
        case AddInstruction: longint of
          AddByte : (AdjustmentByte: shortint);
          AddLong : (AdjustmentLong: longint);
        end;
        PInterfaceMT = ^TInterfaceMT;
        TInterfaceMT = packed record
          QueryInterfaceThunk: PAdjustSelfThunk;
        end;
      TInterfaceRef = ^PInterfaceMT;
    var
      QueryInterfaceThunk: PAdjustSelfThunk;
    begin
      Result := Pointer(I);
      if Assigned(Result) then
        try
          QueryInterfaceThunk := TInterfaceRef(I)^.QueryInterfaceThunk;
          case QueryInterfaceThunk.AddInstruction of
            AddByte: Inc(PChar(Result), QueryInterfaceThunk.AdjustmentByte);
            AddLong: Inc(PChar(Result), QueryInterfaceThunk.AdjustmentLong);
          else
            Result := nil;
          end;
        except
          Result := nil;
        end;
    end;
    résultat plus besoin du IImplementeurProvider
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
      if GetImplementingObject(Test) is TEventMulticaster then
        (...)
    Comme le signal un commentaire de l'article, ceci n'est pas au principe de l'objet. mais bon ça m'a aidé et aidera peut être d'autres aussi.

    voilà.
    Akim Merabet

  2. #2
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 448
    Points
    28 448
    Par défaut
    mouais...personnellement j'ajouterais plutôt un GetInstance dans une interface de base de mon appli. ça évite d'ajouter une nouvelle interface, et ça évite surtout d'utiliser un code qui risque bien de ne pas supporter un changement de version de Delphi

    on alors tu ajoutes un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    {$IFNDEF VERxxx} ATTENTION ! il faut vérifier ce code !!! {$ENDIF}
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  3. #3
    Membre éclairé Avatar de Kaféine
    Homme Profil pro
    Inscrit en
    Avril 2007
    Messages
    569
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 569
    Points : 736
    Points
    736
    Par défaut
    Citation Envoyé par Paul TOTH
    mouais...personnellement j'ajouterais plutôt un GetInstance dans une interface de base de mon appli. ça évite d'ajouter une nouvelle interface, et ça évite surtout d'utiliser un code qui risque bien de ne pas supporter un changement de version de Delphi

    oui c'est d'ailleurs ce que j'ai fait avec le
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
      IImplementorProvider = interface [GUID]
        function GetImplementeur: TObject;
      end
    Une interface de base, non car j'ai juste eu besoin de ça de façon ponctuelle.

    Tout code comporte plus ou moins un risque potentiellement de ne pas supporter un changement de version de Delphi non?
    et la fonction GetImplementingObject ne déroge pas à la règle.

    Enfin le but de ce post était de montrer cette fonction que je trouve plutôt sympa, evidemment on peut toujours faire autrement, d'ailleurs je faisais autrement.
    Akim Merabet

  4. #4
    Membre du Club
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    42
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2008
    Messages : 42
    Points : 51
    Points
    51
    Par défaut
    Bonjour,

    pourquoi pas aller au bout de la logique des interfaces et mettre l'interface IIsEventMulticaster
    dans la classe TEventMulticaster ?

    donc alors on teste comme ca :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    if supports(Test, IIsEventMulticaster) then ...
    IIsEventMulticaster peut être une définition d'interface vide (sans propriété ni rien..) et sert alors juste comme un flag dans TEventMulticaster pouvant aider à déterminer que toute interface vers une instance d'objet descendant de TEventMulticaster (l'interface ITest par exemple) fonctionne comme un TEventMultiCaster.

    En fait moi aussi j'avait eu un besoin similaire, et j'avais utilisé la routine sur HallVard's Blog,
    mais j'en suis revenu, car je voulais vraiment avoir des unités sans mettre dans les Uses des liens vers les classes implémentant
    mes interfaces (je crée même les instances d'objets via un ClassManager, juste avec le Classname, donc sans avoir à inclure dans mes unités travaillant avec des interfaces, les unités de définition des classes)
    cela est trés pratique par exemple pour les vues (frames) et ça me permet d'avoir un code beaucoup plus souple et facilement réutilisable, ce qui est une des vocations des interfaces.

    Comme c'est la méthode que j'utilise, je serait intéressé de savoir si vous pensez que cette technique peut présenter, ou pas, des inconvénients, afin de progresser et éventuellement réviser mes habitudes de code.

  5. #5
    Membre éclairé Avatar de Kaféine
    Homme Profil pro
    Inscrit en
    Avril 2007
    Messages
    569
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 569
    Points : 736
    Points
    736
    Par défaut
    Salut,

    Citation Envoyé par DenDembe
    pourquoi pas aller au bout de la logique des interfaces et mettre l'interface IIsEventMulticaster
    dans la classe TEventMulticaster ?
    En effet, ceci est une possibilité. Néanmoins ça résout que le problème du test si l'objet est un TEventMulticaster. La finalité du test est de caster en TEventMulticaster pour appeler une de ses méthodes
    La solution serait que IIsEventMulticaster ait une méthode genre GetImplementor comme dans le post précédent ou qu'il dérive d'une interface de base avec une méthode GetInstance comme l'a suggéré Paul TOTH du coup on revient à ma solution "Version 1" de départ remplacée avec la "Version 2" qui utilise la fonction GetImplementingObject.

    Citation Envoyé par DenDembe
    En fait moi aussi j'avait eu un besoin similaire, et j'avais utilisé la routine sur HallVard's Blog,
    mais j'en suis revenu, car je voulais vraiment avoir des unités sans mettre dans les Uses des liens vers les classes implémentant mes interfaces (je crée même les instances d'objets via un ClassManager, juste avec le Classname, donc sans avoir à inclure dans mes unités travaillant avec des interfaces, les unités de définition des classes)
    J'ai du mal à saisir ton approche. si j'ai bien compris ton ClassManager te renvois une IInterface quand tu lui passe en paramètre une classname?
    Comment fait tu précisément?


    Pour ce qui me concerne, je declare l'interface dans la "partie interface" de l'unité par contre la déclaration de l'objet implémentant l'interface ainsi que son implémentation dans la "partie Implémentation" (j'ai l'impression de faire des répétitions :\) par conséquent l'implémentation est masquée.
    J'ajoute simplement une fonction ou plusieurs fonctions overloadées qui me renvois une nouvelle instance d'objet implémentant cette interface.
    Akim Merabet

  6. #6
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 448
    Points
    28 448
    Par défaut
    moi non plus j'ai pas compris ton histoire DenDem...

    ça me fait penser que je proposais il y a longtemps déjà d'utiliser les interfaces pour casser la référence circulaire des unités (méthode 3)
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  7. #7
    Membre du Club
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    42
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2008
    Messages : 42
    Points : 51
    Points
    51
    Par défaut
    Bonjour,

    Citation Envoyé par Kaféine Voir le message
    Néanmoins ça résout que le problème du test si l'objet est un TEventMulticaster. La finalité du test est de caster en TEventMulticaster pour appeler une de ses méthodes
    à ce moment là IIsEventMultiCaster peut devenir IEventMultiCaster
    et contenir, dans la déclaration d'interface, la (les) méthode(s) de TEventMulticaster que l'on souhaite appeler...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    ...
    var iemc:IEventMultiCaster;
    begin
       ...
       if supports(test,IEventMultiCaster,iemc) then iemc.MethodeDeIEventMultiCaster;


    J'ai du mal à saisir ton approche. si j'ai bien compris ton ClassManager te renvois une IInterface quand tu lui passe en paramètre une classname?
    Comment fait tu précisément?

    Comme ca :


    les classes enregistrées doivent descendre d'une classe de base, afin d'être manipulables via IInterface :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
      // Ici Il est important de définir un constructeur en Virtual
      // car le Create de TObject ne l'est pas.
      TIntfObjectPlus = class(TInterfacedObject)
        constructor Create; overload; virtual;
      end;
      TIntfClass = class of TIntfObjectPlus;

    Enregistrement d'une classe dans le ClassManager :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    procedure TClassManager.RegisterClass(ClassName: String;  ObjectClass: TIntfClass );
    var
      ClassItem: TClassManagerItem;
    begin
     
      ClassItem := nil;
      try
        ClassItem := FindClass(ClassName);
        if ClassItem <> nil then begin
            raise EClassManagerError.Create(Format('%s déja enregistrée', [ClassName]));
        end else begin
          ClassItem := TClassManagerItem.Create;
          ClassItem.FClassName      := ClassName;
          ClassItem.FObjectClass    := ObjectClass;
          FClasses.AddObject(ClassName, ClassItem); //FClasses est une TStringList
        end;
        ClassItem := nil;
      finally
        ClassItem.Free;
      end;
    end;

    donc pour chaque classe enregistrée, on a ClassName et FObjectClass qui est de type : class of TIntfObjectPlus;

    la création d'une instance à partir d'un TClassManagerItem est simple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    function TClassManagerItem.CreateInstance: IInterface;
    begin
      Result := nil;
      if FObjectClass <> nil then Result := FObjectClass.Create
     
      if Result = nil then
        raise EClassManagerError.Create(Format('Classe %s mal enregistrée dans le ClassManager', [FClassName]));
     
    end;
    Attention ce ne sont que des extraits simplifiés pour illustrer principe, je peut fournir un code complet & fonctionnel si qqun le souhaite.

  8. #8
    Membre éclairé Avatar de Kaféine
    Homme Profil pro
    Inscrit en
    Avril 2007
    Messages
    569
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 569
    Points : 736
    Points
    736
    Par défaut
    Salut,

    Citation Envoyé par DenDemble
    à ce moment là IIsEventMultiCaster peut devenir IEventMultiCaster
    et contenir, dans la déclaration d'interface, la (les) méthode(s) de TEventMulticaster que l'on souhaite appeler...
    Exact! finalement j'ai déclaré IEventMulticaster contenant la méthode Remove dont j'ai besoin ainsi que 2 fonctions
    me renvoyant les 2 attributs (ce qui me genait a priori mais finalement c'est pas grave)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
      IEventMulticaster = interface [GUID]
        function Remove(const OldE: IEvents): IEvents;
        function EventsA: IEvents;
        function EventsB: IEvents;
      end;
    du coup dans ma fonction Contains je fais:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    class function TEventMulticaster.Contains(const E, OldE: IEvents): Boolean;
    var
      MCIntf: IEventMulticaster;
    begin
      if E = OldE then
        Result := True
      else if Supports(E, IEventMulticaster, MCIntf) then
        Result := TEventMulticaster.Contains(MCIntf.EventsA, OldE) or
        TEventMulticaster.Contains(MCIntf.EventsB, OldE)
      else
        Result := Supports(OldE, IEventMulticaster, MCIntf) and
          (TEventMulticaster.Contains(MCIntf.EventsA, OldE) or
          TEventMulticaster.Contains(MCIntf.EventsB, OldE));
    end;
    Effectivement, c'est une autre solution.
    Merci


    Pour ce qui concerne ton ClassManager, moi j'ai un objet similaire au tient je crois.

    J'ai une interface IObjectManager (oui je sais encore une interface...mais c'est trop cool à l'usage)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
      IObjectManager = interface [GUID]
        function GetObject(const AKey: Variant): IInterface;
        procedure FreeObject(const AKey: Variant);
      end;
    et une interface simple nommée IObjectFactory

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
     IObjectFactory = interface [GUID]
        function NewObject: IInterface;
     end;
    TObjectManager prend en paramètre de son constructeur un IObjectFactory.

    Evidemment je declare un ObjectManager pour un type d'objet donné.
    par exemple pour un objet du type IToto, je declare donc TTotoFactory que je passe à TTotoManager.

    du coup quand j'ai besoin d'une nouvelle instance de IToto, j'appelle GetObject('MonToto1') de TTotoManager. Evidemment si MonToto1 existe déjà, le manager ne le recré pas.

    c'est pas tout à fait identique au tient mais dans le même esprit je pense.
    on s'en rapprocherait en déclarant un manager de manager qui serait en fait une Hashmap de IObjectManager :\
    Akim Merabet

  9. #9
    Membre éclairé Avatar de Kaféine
    Homme Profil pro
    Inscrit en
    Avril 2007
    Messages
    569
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 569
    Points : 736
    Points
    736
    Par défaut
    salut,

    Citation Envoyé par Paul TOTH
    ça me fait penser que je proposais il y a longtemps déjà d'utiliser les interfaces pour casser la référence circulaire des unités (méthode 3)
    J'ajouterais une 4ème méthode qui n'est qu'une variante de ta méthode 3 si tu me le permet. on peux l'appeler méthode 3bis .

    je viens de voir qu'il existe une interface (en tout cas depuis D2007 que j'utilise aujourd'hui) que TComponent implémente, qui s'appelle IInterfaceComponentReference contenant une fonction GetComponent.
    On peux donc remplacer l'interface ICDComponent par celle-ci

    Par contre il nous reste l'événement à gérer. Pour cela on déclare une interface pour une collection quelconque avec une méthode Update.
    TCDComponent a juste à implémenter cette interface et s'enregistre auprés de la collection via une methode de celle-ci. on stocke ca reference dans une InterfaceList qui nous permettra également d'enregistrer plus d'un objet qui écouterais la collection.

    Ensuite on peut continuer avec un ICollection, un ICollectionItem..... pour une abstraction encore plus fine.
    Akim Merabet

Discussions similaires

  1. Réponses: 0
    Dernier message: 10/05/2013, 10h32
  2. Réponses: 2
    Dernier message: 27/05/2010, 10h18
  3. Deux ptt problemes avec les interface graphiques
    Par hidalg007 dans le forum NetBeans
    Réponses: 2
    Dernier message: 23/02/2009, 04h15
  4. [1.x] Retours d'expérience avec Symfony
    Par ygrim dans le forum Symfony
    Réponses: 6
    Dernier message: 05/09/2007, 15h13

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo