menu
  Home  ==>  articles  ==>  prog_objet_composants  ==>  interfaces_delphi   

Les Interfaces Delphi - John COLIBRI.


1 - Delphi et les Interfaces

Le langage objet de Delphi est fondamentalement un langage objet, avec des extensions, comme les références de classe ou les méta classes (RTTI).

Pour pouvoir utiliser les objets COM Windows, Delphi a mis en oeuvre les Interfaces. Comme ce modèle de programmation a été jugé suffisamment utile, Delphi a été doté d'un mécanisme d'Interfaces qui ne dépend pas nécessairement de la mécanique COM.

Cet article a pour but de présenter ce que les Interfaces apportent de plus que le modèle objet de base n'offrait pas. Nous allons examiner

  • le modèle objet de Delphi
  • définition
  • les Interfaces comme outil de construction de librairies
  • les Interfaces et le polymorphisme



2 - Le modèle objet Delphi

Delphi utilise un modèle objet à héritage unique. Une Classe ne peut hériter que d'une seule Classe.



Prenons le cas d'un traitement de texte. Nous souhaitons pouvoir:

  • gérer la manipulation du texte (un tampon, avec ajout, modification, recherche, correction orthographique)
  • sauvegarder, recharger les données depuis le disque. Puis renommer, copier, envoyer le fichier sur le réseau etc
La solution simple est de placer toutes ces fonctionalités dans une classe c_editeur:

editeur



Mais si nous devons par la suite gérer un tableur il faudra dupliquer une partie des fonctionalités de gestion de fichier:

editeur_et_tableur



L'idée nous vient naturellement de factoriser la partie fichier dans une classe c_fichier, et hériter de ces fonctionnalités fichier pour notre éditeur et notre tableur :

fichier_texte_et_tableur



Mais si nous souhaitons ajouter d'autres possibilités (dessin graphique, journal, vérification mémoire...), nous ne pourrons pas dériver nos classes "applicatives" de toutes ces classes "utilitaires".

Pour ajouter une classe de calcule statistique, nous plaçons l'évaluation d'expression dans une classe séparée. Toutefois notre tableur ne pourra pas hériter de cette classe, car il hérite déjà de c_fichier:

fichier_texte_et_tableur



La solution en Delphi est d'utiliser la composition: la classe c_tableur contiendra un attribut m_c_fichier, un attribut m_c_evaluateur etc.

composition



La composition nous permet donc d'isoler les groupes de fonctionnalités dans des classes séparées, et de composer ces classes pour obtenir une librairie sans redondance excessive.

Nous pouvons alors construire des structures (des listes, par exemple), qui contiennent des objets du type de base ou d'un de ses descendants:

Var ma_list_fichierArray Of c_fichier;

  ma_liste_fichier.ajoute_fichier(c_editeur.create);
  ma_liste_fichier.ajoute_fichier(c_tableur.create);

et nous pourrons appeler des méthodes qui auront comme paramètre un type c_fichier ou un de ses descendants:

Procedure concatene(p_c_fichierc_fichier); 
  Begin
  End;

For indice_fichier:= 0 To Length(ma_liste_fichier)- 1 Do 
  concatene(ma_liste_fichier[indice_fichier]);



Toutefois nous ne pouvons pas utiliser le polymorphisme pour les classes qui ne font pas partie de la même hiérarchie:

Var ma_list_fichierArray Of c_fichier

  ma_liste_fichier.ajoute_fichier(c_editeur.create);
  ma_liste_fichier.ajoute_fichier(c_statistiques.create); // <= REFUSE

Pour construire une liste de c_fichier et de c_statistique, il faut leur trouver un ancêtre commun. Ici c'est tObject. Nous pourrons donc utiliser une tObjectList. Mais tObject n'a pas les méthodes evalue_expression, ou les attributs de ces Classes, et le moindre traitement exigera force surtypage, Is ou As:

Var ma_list_objettObjectList

  ma_liste_objet.Add(c_editeur.create);
  ma_liste_objet.Add(c_statistiques.create); 
  
  For indice_objet:= 0 To ma_list_objet.Count- 1 Do
    If ma_liste_objet[indice_fichierIs c_fichier
      Then concatene(c_fichier(ma_liste_objet[indice_fichier]));



Fondamentalement, ce qui manque, c'est la possibilité d'ajouter à c_tableur, en plus des méthodes de c_fichier, les méthodes de c_evaluateur.

C'est ce que permettent les Interfaces Delphi. Nous pourrons alors

  • créer des Classes ayant différentes fonctionnalités (traitement de texte, calcul matriciel, tableur, )
  • ajouter à ces Classes des possibilités de sauvegarde, compression, cryptage, génération d'état
  • si nous créons des objets composites, nous savons que nous pourrons effectuer sur ces composites les traitements de sauvegarde, compression, génération d'état etc (polymorphisme)



3 - Définition des Interfaces Delphi

3.1 - La calculette de base

Commençons par un exemple simple d'une mini-calculette avec des possibilités de log.

Supposez qu'un de nos projet utilise une calculette:

Unit u_c_calculator;
  Interface
    Type c_calculator=
              Class
                m_onem_twoInteger;
                m_resultInteger;

                Procedure add;
                Procedure subtract;
              End;

  Implementation

    // --  c_calculator

    Procedure c_calculator.add;
      Begin

      End;

    Procedure c_calculator.subtract;
      Begin

      End;

  End.



3.2 - Ajout d'un log

Nous souhaitons à présent doter des classes de notre projet d'un mécanisme de log. Nous définissons les méthodes qui permettront ce log. Pour pouvoir ajouter ces fonctionnalités à n'importe quelle Classe, nous définissons ces méthodes sous forme d'Interface:

Unit u_i_logable;
  Interface
    Type i_logable=
              Interface
                Procedure initialize_log(p_file_nameString);
                Procedure write_to_log(p_messageString);
              End;

  Implementation

  End.



Notez que :

  • une Interface Delphi se déclare donc avec une syntaxe similaire à une Classe: dans un Type, entre Interface et End
  • Cette Interface définit simplement la syntaxe des procédures et fonctions que nous souhaitons utiliser pour écrire dans notre log. C'est une pure définition. Elle ne fait aucun traitement. Elle permettra de normaliser les noms et paramètres à utiliser pour notre traitement. L'écriture du log elle-même dépendra des données à écrire, et sera donc mise en oeuvre dans les différentes Classes qui auront décidé d'employer le log en utilisant ces méthodes avec ces paramètres.
  • au niveau de la notation:
    • nous avons utilisés les préfixes de la notation alsacienne, avec un "i_" comme préfixe de l'Interface. En notation standard on utiliserait iLog
    • nous avons ajouté le suffixe ABLE qui souligne le fait que cette Interface indiquera qu'une Classe qui utilisera cette Interface sera doté des fonctionnalités de log. Elle sera "capable de" générer des logs. Ce suffixe "able" est aussi optionnel


3.3 - Une Classe qui implémente l'Interface

Modifions à présent notre calculette pour la doter des possibilités de log.

Unit u_c_calculator_2;
  Interface
    Uses Classesu_i_logable;

    Type c_calculator=
              Class(tInterfacedObjecti_logable)
                m_onem_twoInteger;
                m_resultInteger;

                m_c_streamtStream;

                Procedure add;
                Procedure subtract;

                // -- i_log implementation
                Procedure initialize_log(p_file_nameString);
                Procedure write_to_log(p_messageString);
              End;

  Implementation
    Uses SysUtils;
    
    // --  c_calculator

    Procedure c_calculator.add;
      Begin
        write_to_log('add 'IntToStr(m_one)+ '+ 'IntToStr(m_two));
      End// add

    Procedure c_calculator.subtract;
      Begin
      End// subtract

    Procedure c_calculator.initialize_log(p_file_nameString);
      Begin
        m_c_stream:= tFileStream.Create(p_file_namefmCreate);
      End// initialize_log

    Procedure c_calculator.write_to_log(p_messageString);
      Begin
        m_c_stream.Write(p_message[1], Length(p_message));
      End// write_to_log

  End.



Notez que

  • la Classe descend de tInterfacedObject, ce qui permet d'ajouter une ou plusieurs Interfaces dans l'en-tête de la Classe
  • elle DOIT redéfinir entre CLASS et END toutes les méthodes définies dans i_log (initialize_log et write_to_log), et elle DOIT les implémenter
  • par définition ces méthodes sont Public et Virtual / Override
  • l'en-tête de la Classe

    Type c_calculatorClass(tInterfacedObjecti_logable)

    indique clairement que cette classe pourra envoyer des messages de log




4 - Syntaxe

4.1 - L'unité SYSTEM.PAS

Les Interfaces sont définies dans l'unité SYSTEM.PAS

Nous y trouvons:

Type IInterface =
         Interface
           ['{00000000-0000-0000-C000-000000000046}']
           Function QueryInterface(Const IIDTGUIDOut Obj): HResultStdcall;
           Function _AddRefIntegerStdcall;
           Function _ReleaseIntegerStdcall;
         End;

     IUnknown = IInterface;

     TInterfacedObject=
         Class(TObjectIInterface)
           Protected
             FRefCountInteger;
             Function QueryInterface(Const IIDTGUIDOut Obj): HResultStdcall;
             Function _AddRefIntegerStdcall;
             Function _ReleaseIntegerStdcall;
           Public
             Procedure AfterConstructionOverride;
             Procedure BeforeDestructionOverride;
             Class Function NewInstanceTObjectOverride;

             Property RefCountInteger read FRefCount;
         End;

Function TInterfacedObject.QueryInterface(Const IIDTGUIDOut Obj): HResult;
  Begin
    If GetInterface(IIDObj
      Then Result := 0
      Else Result := E_NOINTERFACE;
  End// QueryInterface

Function TInterfacedObject._AddRefInteger;
  Begin
    Result := InterlockedIncrement(FRefCount);
  End// _AddRef

Function TInterfacedObject._ReleaseInteger;
  Begin
    Result := InterlockedDecrement(FRefCount);
    If Result = 0 
      Then Destroy;
  End;



4.2 - iInterface

Toutes les Interfaces Delphi descendent de iInterface. Cette Interface a été définie pour correspondre exactement à une Interface COM Windows, avec 3 méthodes, QueryInterface, _AddRef et _Release.

Donc lorsque nous déclarons:

Type i_mon_interface
         Interface
           Procedure additionne;
         End;

l'héritage de iInterface est implicite. Il est donc équivalent d'écrire:

Type i_mon_interface
         Interface(iInterface)
           Procedure additionne;
         End;



4.3 - tInterfacedObject

SYSTEM.PAS contient la Classe tInterfacedObject, qui fournit une implémentation par défaut des 3 méthodes de iInterface. Le code de ces méthodes figure plus haut.

Voyons à présent à quoi servent ces trois méthodes et comment les utiliser.



4.4 - QueryInterface - GUID

Cette méthode, définie au coeur de COM, permet de tester si un objet COM implémente une Interface donnée. Si nous chargeons un objet COM (un ActiveX), nous pouvons tester si cet objet sait analyser une document XML, arrive à afficher une page .HTML, saura jouer une animation Shockwave Flash.

Ces objets COM peuvent avoir été créés par d'autres personnes, et nous ne disposons en général ni des sources, ni même souvent d'une doc. Nous pouvons donc interroger l'objet pour savoir s'il sait effectuer certains traitements en répondant à certains appels de méthodes.



Pour arriver à effectuer ce test, les Interfaces doivent être dotées d'un identificateur unique, un GUID (Globally Unique Identifier), généré par Windows. Chaque GUID est GARANTI unique, "in the world". Delphi définit ce Guid par:

Type TGUID = Packed Record
               D1LongWord;
               D2Word;
               D3Word;
               D4Array[0..7] Of Byte;
             End;

Pour doter une de nos Interfaces de son propre GUID, il suffit de taper Shift Ctrl G entre Interface et End. Dans notre cas, nous obtenons, par exemple:

Type i_computable=
          Interface
            ['{508B660C-E175-4832-863D-62922D2E4038}']  // <= Shift Ctlr G
            Procedure add;
            Procedure substract;
          End// i_computable



Nous pouvons tester si une Classe peut calculer par:

Type c_computer=
          Class(tInterfacedObjecti_computable)
            m_onem_twoInteger;
            m_resultInteger;

            Procedure add;
            Procedure substract;
            Procedure multiply;
          End// c_computer

Procedure c_computer.add;
  Begin
    display('add');
  End// add

Procedure c_computer.substract;
  Begin
    display('subtract');
  End// substract

Procedure c_computer.multiply;
  Begin
    display('mul');
  End// multiply

Const k_guidtGuid='{508B660C-E175-4832-863D-62922D2E4038}'// <== same guid litteral

Procedure TForm1.query_k_guid_Click(SenderTObject);
  Var l_c_computerc_computer;
      l_i_computablei_computable;
  Begin
    l_c_computer:= c_computer.Create;

    // -- query and extract the interface
    If l_c_computer.QueryInterface(k_guidl_i_computable)= S_ok
      Then Begin
          display('ok');
          l_i_computable.add// <== use the interface
        End
      Else display('not ok');
  End// query_k_guid_Click



Pour appeler mon_objet.QueryInterface nous avons du créer une constante dont la valeur correspond au GUID de noter Interface. Delphi permet aussi d'utiliser comme premier paramètre de QueryInterface le nom de l'Interface, ce qui est bien plus pratique:

Procedure TForm1.query_ixxx_Click(SenderTObject);
  Var l_c_computerc_computer;
      l_i_computablei_computable;
  Begin
    l_c_computer:= c_computer.Create;
    If l_c_computer.GetInterface(i_computablel_i_computable)
      Then Begin
          display('ok');
          l_i_computable.add;
        End
      Else display('not ok');
    End// query_ixxx_



Notez que

  • ATTENTION, il faut appeler

        mon_objet.QueryInterface( ...

    et pas

        QueryInterface( ...

    qui est la fonction de l'API Windows. Celle-ci fonctionne, évidemment, mais a ses propres règles d'utilisation.

  • mon_objet.QueryInterface ne fonctionne QUE SI l'Interface possède un GUID. Si nous appelons mon_objet.QueryInterface(i_xxx ... et que i_xxx n'a pas de GUID, le compilateur bloque

        "the interface i_xxx has no interface identification".

  • si nous testons une Interface qui n'est pas implémentée par la Classe, le résultat de la fonction mon_objet.QueryInterface n'est pas S_OK


4.5 - Référence Interface

Un Type Interface n'est qu'un Type: c'est une définition, pas une donnée.

Pour utiliser une Interface dans un traitement, il faudra donc

  • que nous définissions une Classe qui implémente cette Interface
  • que nous créions un objet qui est une instance de cette Classe. A cet instant des données sont allouées en mémoire
  • que nous obtenions une référence Interface sur cet objet.


Voici un exemple d'une Interface, un Classe qui l'implémente, avec une méthode quelconque qui a un paramètre du type de cette Interface:

Type i_printer=
          Interface
            ['{D7268B1E-3465-4AA7-B62E-F3BD3856B5C0}']
            Procedure print;
          End// i_printer

     c_letter=
          Class(tInterfacedObjecti_printer)
            Procedure insert_line;
            Procedure print;
          End// c_letter

Procedure c_letter.insert_line;
  Begin
    display('insert_line');
  End// insert_line

Procedure c_letter.print;
  Begin
    display('print');
  End// print

Procedure format_printout(p_i_printeri_printer);
  Begin
    p_i_printer.print;
  End// format_printout



Nous pouvons obtenir cette référence Interface de 3 manières

  • en créant directement la référence:
  • en affectant un objet à la référence
  • en appelant mon_objet.QueryInterface
  • en surtypant un objet par AS


4.5.1 - Création directe

Nous pouvons directement créer une référence Interface par le code suivant

Procedure TForm1.direct_ref_Click(SenderTObject);
  Var l_i_printeri_printer;
  Begin
    l_i_printer:= c_letter.create;

    // -- appel d'une méthode de l'Interface i_printer
    l_i_printer.print;
    // -- appel d'une procédure ayant un paramètre i_printer
    format_printout(l_i_printer);
  End// direct_ref_Click

Notez que

  • la variable référence l_i_printer est déclarée de type i_printer, mais la création se fait par c_letter.Create

    Il s'agit du même mécanisme qui permet de déclarer une variable de type classe ancêtre et créer un descendant:

    Type c_ancestor=
            Class
              m_xm_yInteger;
            End;
         c_descendent=
            Class(c_ancestor)
              m_colorInteger;
            End;

      Var my_c_objectc_ancestor;

        // -- crée un objet de type c_descendant
        my_c_object:= c_descendent.Create;

    Ce qui détermine le type créé, ce n'est pas la déclaration statique, mais le Type utilisé pour l'appel du Constructor

  • nous pouvons affecter un c_letter à une i_printer : les références Interfaces de type i_printer sont donc compatibles avec les Classes qui implémentent i_printer, ici c_letter.

    Autrement formulé, nous pourrons remplacer une référence i_printer par une variable c_letter (compatibilité en affectation, ou encore principe de substitution de Liskov)



4.5.2 - Affectation d'un objet

Nous pouvons aussi créer un objet et l'affecter à une référence Interface :

Procedure TForm1.affectation_Click(SenderTObject);
  Var l_c_letterc_letter;
      l_i_printeri_printer;
  Begin
    l_c_letter:= c_letter.Create;

    // -- affactation (compatibilité en affectation)
    l_i_printer:= l_c_letter;

    // -- appel d'une méthode de l'Interface i_printer
    l_i_printer.print;
    // -- appel d'une procédure ayant un paramètre i_printer
    format_printout(l_i_printer);

    // -- appel, avec compatibilité en affactation
    format_printout(l_c_letter);
  End// affectation_Click



Notez que

  • la compatibilité en affectation intervient ici de façon plus visible

    l_i_printer:= l_c_letter;
    format_printout(l_c_letter);



4.5.3 - QueryInterface

Nous pouvons aussi obtenir une référence Interface en appelant mon_objet.QueryInterface, comme nous l'avons montré plus haut.



4.5.4 - Transtypage par As

Nous pouvons finalement convertir un objet en une référence Interface en utilisant une version de As:

Procedure TForm1.conversion_as_Click(SenderTObject);
  Var l_c_letterc_letter;
      l_i_printeri_printer;
  Begin
    l_c_letter:= c_letter.Create;

    // -- affactation (compatibilité en affectation)
    l_i_printer:= l_c_letter As i_printer;
    l_i_printer.print;
    format_printout(l_i_printer);

    // -- appel, avec compatibilité en affactation
    format_printout(l_c_letter As i_printer);
  End// conversion_as_Click



Notez que

  • As ne fonctionne que si notre Interface a été déclarée avec un GUID


4.5.5 - Interface et référence Interface

L'Interface ne constitue qu'une définition de fonctionnalités.

Nous ne pouvons utiliser ces fonctionnalités que sur un objet que implémente cette Interface.

Nous pouvons, bien sûr, créer un objet et appeler directement les méthodes ce cet objet. Mais si nous ne sommes intéressés que par les méthodes de l'Interface, ou si nous avons une structure avec différents objets, et ne sommes intéressés que par les fonctionnalités de l'Interface, nous pouvons utiliser une référence Interface en appelant les méthodes de l'Interface.



De façon imagée

  • nous pouvons considérer les objets comme des icebergs:

    interface_reference_1

  • pour certains traitements, nous ne sommes intéressés que par le sommet de ces icebergs.

    interface_reference

    Pour dessiner, il faut bien que l'objet complet existe, mais nous ne sommes intéressés que par les fonctionnalités de dessin

  • les références Interface pointent bien sur de véritables objets, mais en ne dévoilant qu'une partie des traitements disponibles sur cet objet.

    En fait, cette image n'est pas très éloignée de la réalité. L'article Dump Interface permet d'afficher à la fois

    • le pointeur objet
    • les pointeur des différentes référence Interfaces de ce même objet.
    Dans ces conditions, l'affectation, ou le surtypage par As ne font que calculer la référence Interface à partir du pointeur vers l'objet.


4.5.6 - Accès aux méthodes

Il est donc acquis que lorsque nous avons une référence Interface, il existe bien un objet qui permet d'appeler ces méthodes. Il existe en général, en plus, des données et d'autres méthodes, propre à cet objet.

Mais lorsque nous utilisons le pointeur Interface, nous ne pouvons PAS y accéder. Nous ne pouvons appeler QUE les méthodes de l'Interface.

L'Interface correspond donc à une vue partielle, une facette, de cet objet. Mais c'est bien là tout le concept: si nous utilisons une référence Interface mon_i_log, nous ne sommes intéressés que par les appels des méthodes de i_log. L'objet sous-jacent fera son traitement propre. Il sait, lui, ce qu'il doit placer dans le log et comment le faire.

Ce comportement est très similaire à Virtual: quand vous appelez mon_triangle.dessine ou mon_cercle.dessine, c'est chaque objet qui sait le mieux comment dessiner son triangle ou son cercle (et pas la figure ou le programme principal).



4.6 - Gestion Mémoire

Pour imiter le fonctionnement COM, Delphi a lié les Interface à une libération automatique de la mémoire.

Les objets qui implémentent une Interface sont dotés d'un champ RefCount. Puis:

  • ce champ est incrémenté chaque fois que nous créons une référence Interface sur cet objet
  • il est décrémenté lorsque cette référence Interface est supprimée.
  • lorsque mon_objet.RefCount devient 0, l'objet est libéré.
Notez bien que RefCount est lié à l'OBJET (un Type Interface ne pouvant, lui, avoir de données).



L'augmentation de RefCount se produit lorsque

  • nous créons une référence Interface directement
  • nous affectons un objet à une Interface
La diminution de RefCount est provoquée par
  • l'affectation de Nil à une référence Interface
  • la réaffectation de la référence vers un autre objet implémentant l'Interface
  • la sortie d'une méthode où une référence Interface locale est créée


Voici un projet pour tester RefCount:

Type i_printer=
          Interface
            ['{D7268B1E-3465-4AA7-B62E-F3BD3856B5C0}']
            Procedure print;
          End// i_printer

     c_letter=
          Class(tInterfacedObjecti_printer)
            m_line_countInteger;
            Procedure print;
          End// c_letter

Procedure c_letter.print;
  Begin
    display('print');
  End// print

Var g_c_letterc_letterNil;

Procedure display_letter_refcount(p_textString);
  Begin
    display(p_text' RefCount= 'IntToStr(g_c_letter.RefCount)); 
  End// display_letter_refcount

Procedure format_printout(p_i_printeri_printer);
  Begin
    display_letter_refcount('in_method');
    p_i_printer.print;
  End// format_printout

Procedure TForm1.affectation_Click(SenderTObject);
  Var l_i_printeri_printer;
  Begin
    g_c_letter:= c_letter.Create;
    display_letter_refcount('1_after_create');

    l_i_printer:= g_c_letter;
    display_letter_refcount('2_after_i_assign');

    l_i_printer.print;
    format_printout(l_i_printer);
    display_letter_refcount('3_after_method_1');

    format_printout(g_c_letter);
    display_letter_refcount('4_after_method_2');
  End// affectation_Click

Procedure TForm1.check_object_Click(SenderTObject);
  Begin
    display_letter_refcount('5_global_click');
    g_c_letter.insert_line;
    g_c_letter.m_line_count:= 5;
    If g_c_letter Is c_letter
      Then display('ok');
  End// check_object_Click

et un clic sur "affectation" et "check_objet" affichera

 
1_after_create RefCount= 0
2_after_i_assign RefCount= 1
print
in_method RefCount= 2
print
3_after_method_1 RefCount= 1
in_method RefCount= 2
print
4_after_method_2 RefCount= 1

5_global_click RefCount= 24 <====
insert_line
ok



Notez que

  • RefCount augment après l'affectation à notre référence Interface
  • il monte même à 2 dans la procédure qui a un paramètre i_print (les paramètres valeur sont des copies sur la pile, d'où une seconde référence: la locale dans la procédure appelante et le paramètre dans la procédure)
  • TRES SURPRENANT, RefCount devient incohérent après la fin de la procédure.
    La raison est que avant le End, Refcount était 1, et en quittant la procédure, il tombe à 0, ce qui libère l'objet.

    Nous pouvons encore dans un autre bouton manipuler l'objet (appeler g_c_letter.print, manipuler ses attributs, tester son type). Dans notre cas nous n'avons pas eu systématiquement d'exception. En fait le pointeur g_c_letter pointe vers la même adresse. Mais ce comportement est totalement aléatoire, et fort souvent (pas toujours, hélas), une utilisation de g_c_letter provoquera une exception.

La moralité est qu'il ne faut pas mélanger les objets et les références d'Interfaces n'importe comment:
  • ici nous avons crée un objet global, et ensuite créé une référence d'Interface qui va imposer son cycle de vie à l'objet. Un autre développeur pourrait penser que l'existence de la globale est régie par Create et Free, et ne soupçonnera pas en général la création d'une référence d'Interface
  • si notre c_letter avait été déclaré localement, la destruction n'aurait pas été un problème
  • l'appel d'une procédure ayant un paramètre VALEUR i_printer fonctionne bien sur la globale: la référence est augmentée à l'entrée et décrémentée à la sortie.


Le même type d'incident serait provoqué par une réaffectation de la référence Interface:

Procedure TForm1.reassign_i_printer_Click(SenderTObject);
  Var l_i_printeri_printer;
  Begin
    g_c_letter:= c_letter.Create;

    l_i_printer:= g_c_letter;

    // -- REAFFECTATION à un nouvel objet
    // --  => libère g_c_letter
    l_i_printer:= c_letter.Create;

    // -- sortie de la procédure
    // --  => libère le second objet utilisé par l_i_printer
  End// reassign_i_printer_Click



Nous aurions pu compenser la diminution de RefCount, en incrémentant sa valeur pour qu'elle ne tombe pas à 0. Toutefois

  • RefCount est une propriété en lecture seule
  • _AddRef est Protected au niveau de tInterfacedObject
Plusieurs solutions:
  • écrire un descendant de tInterfacedObject qui rendrait _AddRef Public
  • utiliser un surtypage par iInterface qui nous permet d'accéder à _AddRef:

    Procedure TForm1.call_addref_Click(SenderTObject);
      Var l_i_printeri_printer;
      Begin
        g_c_letter:= c_letter.Create;
        display_letter_refcount('1_after_create');

        l_i_printer:= g_c_letter;
        display_letter_refcount('2_after_i_assign');

        // -- bump RefCount
        iInterface(l_i_printer)._AddRef;
        display_letter_refcount('3_after_AddRef');

        l_i_printer:= c_letter.Create;
        display_letter_refcount('4_after_reassign');
      End// call_addref_Click

  • écrire une Classe similaire à tInterfacedObject pour laquelle _AddRef et _Release ne font rien. Les Interfaces ne gèreront donc plus la libération automatique


4.7 - Diagramme de Classe UML

Nous pouvons résumer la double structure Interface et Classe par le diagramme de Classe UML suivant:

uml_class_diagram

Nous avons dessiné les Interfaces en pointillé, pour bien souligner que ce sont des éléments différents des Classes.

De plus, comme les Classes sont OBLIGEES d'implémenter les méthodes définies dans les Interfaces, nous pouvons, dans ces diagrammes de classe, supprimer ces méthodes des Classes :

uml_class_diagram



4.8 - Résumé sur la création d'Interfacee

Pour définir nos propres Interfaces dans nos projets
  • nous créons des Types Interfaces qui continenent ou non de GUID
  • ces Interfaces seront implémentées par une ou plusieurs Classes qui
    • pratiquement toujours, descendront de tInterfacedObject
    • listeront les Interfaces dans l'en-tête de la définition de la Classe
    • la définition de la Classe contiendra TOUTES les méthodes définies dans ces Interfaces, et ces méthodes seront implémentées dans la partie Implementation de l'Unité contenant cette définition de Classe


4.9 - Interface et Property

En plus des Procedures et Functions, la définition d'une Interface peut comporter des Propertys.

Ces propriétés sont uniquement une facilité de notation, et devront automatiquement comporter les getter et setters. Voici un exemple:

Type i_shape=
          Interface
            Procedure draw;
            Procedure set_size(p_sizeInteger);
            Function get_sizeInteger;
            Property SizeInteger read get_size write set_size;
          End// i_shape

     c_figure=
          Class(tInterfacedObjecti_shape)
            m_sizeInteger;
            Procedure draw;
            Procedure set_size(p_sizeInteger);
            Function get_sizeInteger;
          End// c_figure

Procedure c_figure.draw;
  Begin
  End// draw

Procedure c_figure.set_size(p_sizeInteger);
  Begin
    display('dans c_figure.set_size');
    m_size:= p_size;
  End// set_size

Function c_figure.get_sizeInteger;
  Begin
    display('dans c_figure.get_size');
    Result:= 2* m_size;
  End// get_size

Procedure TForm1.i_shape_Click(SenderTObject);
  Var l_i_shapei_shape;
  Begin
    l_i_shape:= c_figure.create;
    l_i_shape.Size:= 10;
    display(IntToStr(l_i_shape.Size));
  End// i_shape_Click



Des Classes qui implémentent notre Interface pourront même avoir une propriété qui port le même nom, mais qui pourront correspondre à d'autre méthodes d'accès.

Type i_shape=
          Interface
            Procedure draw;
            Procedure set_size(p_sizeInteger);
            Function get_sizeInteger;
            Property SizeInteger read get_size write set_size;
          End// i_shape

     c_figure=
          Class(tInterfacedObjecti_shape)
            m_sizeInteger;
            m_figure_sizeInteger;
            Procedure draw;
            Procedure set_size(p_sizeInteger);
            Function get_sizeInteger;

            Procedure set_figure_size(p_sizeInteger);
            Function get_figure_sizeInteger;
            Property SizeInteger read get_figure_size write set_figure_size;
          End// c_figure

Procedure c_figure.draw;
  Begin
  End// draw

Procedure c_figure.set_size(p_sizeInteger);
  Begin
    display('dans c_figure.set_size');
    m_size:= p_size;
  End// set_size

Function c_figure.get_sizeInteger;
  Begin
    display('dans c_figure.get_size');
    Result:= 2* m_size;
  End// get_size

Procedure c_figure.set_figure_size(p_sizeInteger);
  Begin
    display('dans c_figure.set_figure_size');
    m_figure_size:= p_size;
  End// set_figure_size

Function c_figure.get_figure_sizeInteger;
  Begin
    display('dans c_figure.get_figure_size');
    Result:= 3* m_figure_size;
  End// get_figure_size

Procedure TForm1.i_shape_Click(SenderTObject);
  Var l_i_shapei_shape;
  Begin
    l_i_shape:= c_figure.create;
    l_i_shape.Size:= 10;
    display(IntToStr(l_i_shape.Size));
  End// i_shape_Click

Procedure TForm1.c_figure_Click(SenderTObject);
  Begin
    With c_figure.create Do
    Begin
      Size:= 100;
      display(IntToStr(Size));
      Free;
    End// with c_figure.create
  End// c_figure_Click

Cette pratique est naturellement encline à encourager la confusion et est peu recommandée. Elle existe pour résoudre des conflits si vous ne possédez pas les sources des classes et interfaces ancêtres



4.10 - Héritage d'Interfaces

Une Interface peut hériter d'une autre Interface. La classe qui implémentera l'Interface descendante devra implémenter les méthodes des deux niveaux.

Voici un exemple:

Type i_one=
          Interface
            Procedure method_one;
          End// i_one
     i_two=
          Interface(i_one)         // <= héritage d'Interface
            Procedure method_two;
          End// i_two
     c_class=
          Class(tInterfacedObjecti_two)
            Procedure method_one;
            Procedure method_two;
          End// c_class

Procedure c_class.method_one;
  Begin
  End// method_one

Procedure c_class.method_two;
  Begin
  End// method_two



Une Classe peut implémenter une Interface, et un descendant peut implémenter un descendant de l'Interface:

Type i_one=
          Interface
            Procedure method_one;
          End// i_one
     c_ancestor_class=
          Class(tInterfacedObjecti_one)
            Procedure method_one;
          End// c_class

Procedure c_ancestor_class.method_one;
  Begin
  End// method_one

Type i_two=
          Interface(i_one)             // <= héritage d'Interface
            Procedure method_two;
          End// i_two
     c_descendent_class=
          Class(c_ancestor_classi_two)  // <= héritage de classe
            Procedure method_two;
          End// c_class

Procedure c_descendent_class.method_two;
  Begin
  End// method_two

C'est en fait la structure déjà rencontrée pour

  • iInterface, i_my_interface
  • avec tObject, tInterfacedObject (qui implémente iInterface) et tMyObject (qui hérite de tInterfacedObject et implémente i_my_interface).


L'utilisation de l'héritage des Interfaces est contestée par certains. Elle revient à imposer aux Classes qui implémente l'Interfacee descendante toutes les méthodes. Or, dans l'esprit des Interfaces, nous préférerions laisser chaque Classe implémenter les Interfaces qu'elle souhaite.

En fait lorsqu'une Classe implémente une Interface descendante

  • elle DOIT bien implémenter les méthodes de cette Interface et de son Interface ancêtre
  • en revanche, les les méthodes de l'Interface ancêtre ne seront PAS accessibles par une référence Interface.

    Voici le diagramme de Classe:

    interface_inheritance_detail

    et une référence d'Interface i_a ne POURRA PAS être créée à partir d'un objet c__b_direct.

    En code:

    Type i_a=
              Interface
                Procedure a;
              End// i_one
         i_b=
              Interface(i_a)
                Procedure b;
              End// i_two
         c__b_direct=
              Class(tInterfacedObjecti_b)
                Procedure a;
                Procedure b;
              End// c_class
         c__a_and_b=
              Class(tInterfacedObjecti_ai_b)
                Procedure a;
                Procedure b;
              End// c_class
         c__a=
              Class(tInterfacedObjecti_a)
                Procedure a;
              End// c_class
         c__a_plus_b=
              Class(c__ai_b)
                Procedure a;
                Procedure b;
              End// c_class

    Procedure c__b_direct.a;
      Begin
      End;

    Procedure c__b_direct.b;
      Begin
      End;

    Procedure c__a_and_b.a;
      Begin
      End;

    Procedure c__a_and_b.b;
      Begin
      End;

    Procedure c__a.a;
      Begin
      End;

    Procedure c__a_plus_b.a;
      Begin
      End;

    Procedure c__a_plus_b.b;
      Begin
      End;

    Procedure TForm1.b_direct_Click(SenderTObject);
      Var l_i_ai_a;
      Begin
        // NO l_i_a:= c__b_direct.create;
      End;

    Procedure TForm1.a_and_b_Click(SenderTObject);
      Var l_i_ai_a;
      Begin
        l_i_a:= c__a_and_b.create;
      End// a_and_b_Click

    Procedure TForm1.a_plus_b_Click(SenderTObject);
      Var l_i_ai_a;
      Begin
        l_i_a:= c__a_plus_b.create;
      End// a_plus_bClick





5 - Interfaces Delphi dans la Vcl, génériques, design patterns

5.1 - Les Interfaces dans la VCL Delphi

Une recherche de "= Interface" dans la VCL Delphi 6 fournit environ 1.600 définitions.

Sur ces 1.600 définitions

  • plus de 1.500 sont liés à la mécanique COM de Windows, dans les domaines
    • MsXml
    • Ado
    • ActiveX
    • ComSvs
    • ShlObj (shell)
    • OleDb
  • les utilisations Delphi se rencontrent surtout dans les domaines
    • dbExpress, DataSnap
    • WebSnap
    • les variants
    • le docking


Pour la partie "purement Delphi" (non lié à COM), nous avons trouvé
  • iDockManager définit la spécification pour le tDockTree, qui est la structure polymorphique
    • "responsible for inserting and removing controls (and thus zones) from the tree and associated housekeeping, such as orientation, zone limits, parent zone creation, and painting of controls into zone bounds"
    et qui est un exemple de structure polymorphique avec des traitements de docking sur les éléments de la structure

  • pratiquement toutes les autres Interfaces sont utilisées
    • pour définir un groupe de méthodes. Par exemple iDsCursor, utilisé comme attribut, paramètre, variable locale:

      Type IDSCursor
               Interface(IUnknown)
                 ...  

      Type TCustomClientDataSet
              Class(TDataSet)
                FFindCursorIDSCursor;
                ...
       
                Procedure SortOnFields(CursorIDSCursor
                    Const FieldsStringCaseInsensitiveDescendingBoolean);
                ...

      Function TCustomClientDataSet.FindRecord(RestartGoForwardBoolean): Boolean;
        Var CursorIDSCursor;
        Begin
          ...

    • pour "accoller" à une Classe des groupes de fonctionnalités additionnelles : la Classe hérite déjà d'une autre Classe (autre que tObject ou tInterfacedObject) et implémente, en plus, d'autres Interfaces :

      Type ISendDataBlock
              Interface
                Function Send(Const DataIDataBlock
                    WaitForResultBoolean): IDataBlockStdcall

           THTTPServer = Class(TWebModuleISendDataBlock) ...
           TSocketDispatcherThread = Class(TServerClientThreadISendDataBlock) ...
           TStreamedConnection = Class(TDispatchConnectionISendDataBlock) ...

      ou encore

      • la définition de tDataSet, qui a les anciennes fonctionnalités BDE, mais permet en plus d'être associé à un tDataSetProvider
      • la définition d'un tSoapDataModule, qui a le même rôle qu'un datamodule usuel (conteneur non graphique) et peut être utilisé comme un serveur d'application :
      Type // -- accole iProviderSupport à tDataSet
           tDatasetClass(tcomponentiprovidersupport)

           // -- accole les fonctionnalités iAppServer à un tDataModule
           tsoapdatamodule = Class(tdatamoduleiappserveriprovidercontainer




5.2 - Utilisation des Interfaces

Voici un exemple simple où nous implémentons un traitement sur les éléments d'une structure. Par exemple pouvoir calculer la surface d'une figure.

Nous définisoons, par exemple

  • une Interface de base pour calculer la surface
  • une Voici la définition des Interfaces:
    • pour calculer la surface, i_surface
    • pour gérer et calculer la surface d'un rectangle
    • pour gérer une liste de figures en vue d'en calculer la surface
Unit u_i_shape_list;
  Interface
    Type i_shape=
              Interface
                Function f_surfaceInteger;
              End;

         i_rectangle=
              Interface(i_shape)
              End;
         i_shape_list=
              Interface(i_shape)
                Procedure add_shape(p_i_shapei_shape);
              End;

  Implementation

  End.



Nous implémentons un c_rectangle et la liste des figures ainsi:

Unit u_c_shape_list;
  Interface
    Uses Classesu_i_shape_list;

    Type c_rectangle=
              Class(tInterfacedObjecti_shapei_rectangle)
                m_widthm_heightInteger;
                Constructor create_rectangle(p_widthp_heightInteger);
                Function f_surfaceInteger;
                Function f_widthInteger;
              End;
         c_shape_list=
              Class(tInterfacedObjecti_shapei_shape_list)
                m_c_shape_listtInterfaceList;

                Constructor create_shape_list;
                Procedure add_shape(p_i_shapei_shape);
                Function f_surfaceInteger;
              End;

  Implementation
    Uses SysUtilsu_c_display;

    // -- c_rectangle

    Constructor c_rectangle.create_rectangle(p_widthp_heightInteger);
      Begin
        m_width:= p_width;
        m_height:= p_height;
      End// create_rectangle

    Function c_rectangle.f_surfaceInteger;
      Begin
        Result:= m_widthm_height;
      End// f_surface

    Function c_rectangle.f_widthInteger;
      Begin
      End;

    // -- c_shape_list

    Constructor c_shape_list.create_shape_list;
      Begin
        m_c_shape_list:= tInterfaceList.Create;
      End// create_shape_list

    Procedure c_shape_list.add_shape(p_i_shapei_shape);
      Begin
        m_c_shape_list.Add(p_i_shape);
      End// add_shape

    Function c_shape_list.f_surfaceInteger;
      Var l_shape_indexinteger;
      Begin
        Result:= 0;

        With m_c_shape_list Do
          For l_shape_index:= 0 To Count- 1 Do
          Begin
            Result:= Resulti_shape(m_c_shape_list[l_shape_index]).f_surface;
          End;
      End// f_surface

  End.



Et finalement un exemple d'utilisation dans la forme principale:

Unit u_c_shape_list;
  Interface
    Uses Classesu_i_shape_list;

    Type c_rectangle=
              Class(tInterfacedObjecti_shapei_rectangle)
                m_widthm_heightInteger;
                Constructor create_rectangle(p_widthp_heightInteger);
                Function f_surfaceInteger;
                Function f_widthInteger;
              End;
         c_shape_list=
              Class(tInterfacedObjecti_shapei_shape_list)
                m_c_shape_listtInterfaceList;

                Constructor create_shape_list;
                Procedure add_shape(p_i_shapei_shape);
                Function f_surfaceInteger;
              End;

  Implementation
    Uses SysUtilsu_c_display;

    // -- c_rectangle

    Constructor c_rectangle.create_rectangle(p_widthp_heightInteger);
      Begin
        m_width:= p_width;
        m_height:= p_height;
      End// create_rectangle

    Function c_rectangle.f_surfaceInteger;
      Begin
        Result:= m_widthm_height;
      End// f_surface

    Function c_rectangle.f_widthInteger;
      Begin
      End;

    // -- c_shape_list

    Constructor c_shape_list.create_shape_list;
      Begin
        m_c_shape_list:= tInterfaceList.Create;
      End// create_shape_list

    Procedure c_shape_list.add_shape(p_i_shapei_shape);
      Begin
        m_c_shape_list.Add(p_i_shape);
      End// add_shape

    Function c_shape_list.f_surfaceInteger;
      Var l_shape_indexinteger;
      Begin
        Result:= 0;

        With m_c_shape_list Do
          For l_shape_index:= 0 To Count- 1 Do
          Begin
            Result:= Resulti_shape(m_c_shape_list[l_shape_index]).f_surface;
          End;
      End// f_surface

  End.



Cerise sur le gâteau: nous pouvons effectuer le calcul sur tForm1:

Unit u_composite;
  Interface
    Uses Windows, ... u_i_shape_list;

    Type TForm1=
             Class(TFormi_shape)
                 compute_surface_TButton;
                 Procedure compute_surface_Click(SenderTObject);
                 ...
               Private
                 Function f_surfaceInteger;
               End// tForm1

    Var Form1TForm1;

  Implementation
    Uses  u_c_shape_list ;

    {$R *.DFM}

    Procedure TForm1.compute_surface_Click(SenderTObject);
      Var l_c_rectanglec_rectangle;
      Begin
        With c_shape_list.create_shape_list Do
        Begin
          l_c_rectangle:= c_rectangle.create_rectangle(10, 20);

          add_shape(l_c_rectangle);
          add_shape(Form1);

          display('rectangle_surface 'IntToStr(l_c_rectangle.f_surface));
          display('Form1_surface     'IntToStr(Form1.f_surface));
          display('total_surface     'IntToStr(f_surface));

          Free;
        End// with c_shape_list
      End// compute_surface_Click

  End.

Nous pouvons ajouter l'Interface i_shape à tForm1, car les tForm implémentent iInterface au niveau de leur ancêtre tComponent

Type TComponent
         Class(TPersistentIInterfaceIInterfaceComponentReference)
           ...



Notez que

  • dans cet exemple, i_rectangle ne sert pas à grand chose
  • pour pouvoir utiliser un rectangle dans notre organisation, il a fallu prendre soin d'incorporer i_shape à c_rectangle (sinon nous n'aurions pas pu appeler add_shape, qui attend un i_shape et pas un i_rectangle)
  • nous avons évité la libération des données, car nous souhaitons bien que notre rectangle local soit libéré, mais pas que Form1 le soit. En fait il faudrait tenir compte du _Release qui est géré de façon particulier pour les tForm


5.3 - Interfaces et .Net

En .Net (== Java), que nous avons présenté dans nos articles Delphi 2006, les Interfaces sont beaucoup plus utilisés. Il n'y a pas de tList ou tObjectList, mais des conteneurs dérivés d'Interfaces collection. En fait la hiérarchie commence beaucoup "plus haut"
  • iEnumerator a la fonction MoveNext et l'élément courant Current (une propriété, naturellement)
  • iEnumerable sait récupérer un énumérateur par la fonction GetEnumérator
  • iCollection et iList sont des descendants de plus en plus étoffés
  • les véritables conteneurs sont Array, ArrayList, CollectionBase
  • nous créons nos propres collections en dérivant de CollectionBase :
dot_net_interfaces

L'utilisation des Interfaces permet à .Net de nicher certaines fonctionnalités à un niveau plus élevé dans la hiérarchie des classes. Par exemple la sensibilité aux données est au niveau d'un Edit (et évite la dichotomie tEdit, tDbEdit)



5.4 - Interfaces et Génériques

Les génériques, introduits d'abord en Delphi 2007 pour la partie .Net de Delphi, on mis en évidence l'utilisation des Interfaces pour définir des groupes de fonctionnalités. Pour créer un conteneur générique avec possibilités de
  • trier (== comparer)
  • dupliquer
  • sauvegarder sur disque
nous pourrions déclarer un type:

Type tSortedList<TiComparable<T>, iCloneable<T>, iSerializable<T> > ...

L'en-tête du type générique liste les contraintes que devront satisfaire les types actuels. De façons imagée, nous pourrions dire que nous allons faire notre marché en choisissant "tiens, mettez moi un peu de triable, et que je pourrais aussi dupliquer, et aussi ...". En fait nous définissons une spécification.



5.5 - Interfaces et Design Patterns

Le afficionados des Design Patterns auront reconnu que notre exemple de liste de surfaces était en fait une implémentation du design pattern "composite"

Ce pattern est recommandé lorsque nous souhaitons pouvoir effectuer les mêmes traitements sur les éléments d'une structure et sur la structure.

Schématiquement, notre hiérarchie ressemble à:

composite



Un diagramme simplifié de "composite" pourrait être :

composite_design_pattern

En définissant i_shape et c_shape_list, nous nous préoccupons de la fonctionnalité pure

  • spécifier les fonctionnalités que nous souhaitons utiliser
  • mettre en oeuvre le conteneur
Lorsque nous aurons à utiliser cette mécanique, il suffira de créer des classes qui implémentent nos Interfaces, c_shape_1, c_shape_2 etc.



En fait, toute la mécanique des Design Pattern repose entièrement sur ce principe: "coding to the interface"

  • nous définissons des organisations d'Interface et de classes ancêtres, pour obtenir la fonctionnalité désirée.
  • puis nous dérivons des classes réelles qui se substitueront aux ancêtres dans l'organisation précédente.
Dans notre exemple
  • "construire une organisation où les éléments et la structure peuvent effectuer les mêmes traitements", tout est défini au niveau de i_shape et c_shape_list
  • le calcul réel est réalisé dans c_rectangle et tForm1
En gros un "framework" définit l'organisation, les classes réelles n'ont plus qu'à effectuer le traitement spécialisé.

En utilisant les Interface et en réfléchissant à l'organisation générale de votre projet plutôt que de coder directement, vous vous élevez du job de programmeur au rang d'architecte, ce qui, naturellement, vous vaudra immédiatement considération, admiration, et justifiera même une une augmentation immédiate, voire une promotion. Plaisanterie mise à part, ces compétences-là seront transposable dans tout environnement, langage ou développements à venir.




6 - Télécharger le code source Delphi

Vous pouvez télécharger: Ce .ZIP qui comprend:
  • le .DPR, la forme principale, les formes annexes eventuelles
  • les fichiers de paramètres (le schéma et le batch de création)
  • dans chaque .ZIP, toutes les librairies nécessaires à chaque projet (chaque .ZIP est autonome)
Ces .ZIP, pour les projets en Delphi 6, contiennent des chemins RELATIFS. Par conséquent:
  • créez un répertoire n'importe où sur votre machine
  • placez le .ZIP dans ce répertoire
  • dézippez et les sous-répertoires nécessaires seront créés
  • compilez et exécutez
Ces .ZIP ne modifient pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le répertoire.

La notation utilisée est la notation alsacienne qui consiste à préfixer les identificateurs par la zone de compilation: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lasse. Elle est présentée plus en détail dans l'article La Notation Alsacienne



Comme d'habitude:

  • nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
  • tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
  • plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
    Nom :
    E-mail :
    Commentaires * :
     

  • et si vous avez apprécié cet article, faites connaître notre site, ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.



7 - Références

Pour les Interfaces
  • Les interfaces d'objet sous Delphi - Laurent Dardenne - Fév 2007. Une introduction détaillé au niveau de la syntaxe des Interfaces Delphi
  • dump interface - J Colibri - Mai 2°°4 - la description de l'organisation mémoire des Classes et Interfaces COM sous Delphi. Démontre que les références d'Interfaces sont un pointeur différent du pointeur de l'objet
  • Diagrammes de Classe UML - J Colibri - présentation des diagrammes de Classe UML
  • Uml avec Delphi : comment créer des diagrammes UML avec Delphi et Together: diagramme de classe, cas d'utilisation et diagramme de séquence. Avec génération de code et synchronisation entre le code Pascal et les diagrammes
  • et tous nos articles Design Pattern en général
  • les articles sur les génériques


Nous présentons naturellement les règles, les bénéfices et les bonnes pratiques d'utilisation des Interfaces dans nos formations
  • Formation Programmation Objet Delphi présentation et pratique des techniques de la programmation orientée objet. Les techniques de bases et de nombreux exercices permettent de maîtriser les Interfaces
  • Formation UML et Design Patterns Delphi Analyse et Conception Objet - UML et les Design Patterns. Une formation présentant UML (entre autre les diagrammes de classe), et les design patterns, qui peuvent pratiquement tous être définis en utilisant des Interfaces


Nous intervenons aussi en tant qu'expert Delphi pour réorganiser les projets de nos clients, soit sous forme de contrats de développement, soit sous forme de transfert de technologie. Et pour ce type de mission, UML, les Interfaces et les design pattern font partie de notre boîte à outil de base.




8 - L'auteur

John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le conseil (composants, architecture, test) et la formation. Son site contient des articles avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client.
Created: jan-04. Last updated: mar-2020 - 250 articles, 620 .ZIP sources, 3303 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2020
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
    + web_internet_sockets
    + services_web_
    + prog_objet_composants
      – dump_interface
      – packages_delphi
      – ecriture_de_composant
      – c_list_of_double
      – interfaces_delphi
      – delphi_generics
      – delphi_rtti
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
    + colibri_skelettons
    + admin
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Formation Initiation Delphi L'outil de développpement, le langage de programmation, les composants - 3 jours
Formation Mise à Niveau Consolider la programmation objet, l'écriture de composants, l'accès aux bases de données, le multi-tâche - 3 jours