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 :

Création d'objets "par lots"


Sujet :

Langage Delphi

  1. #1
    Membre habitué Avatar de phplive
    Profil pro
    Inscrit en
    Avril 2003
    Messages
    179
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2003
    Messages : 179
    Points : 150
    Points
    150
    Par défaut Création d'objets "par lots"
    Bjr

    Voilà j'aimerais savoir s'il existe un moyen de créer des objets par lots donc N objets à la fois sans avoir à faire N fois Create ni à allouer N blocs de mémoire.

    En effet j'essai de charger le contenu d'une table d'une base de données dans une liste d'objets

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    Delphi               <-> Base de données 
     
    TItemList            <-> Table
    +-- TItem            <-> +-- Enregistrements
        +-- TAttribut    <->     +-- Champs
    Sachant que mon enregistrement comporte jusqu'à 30 champs lorsque je charge 1000 enregistrements j'ai déjà 30 000 attributs à créer : or c'est assez long ...

    Le problème provenant spécifiquement de l'allocation des attributs je me demandais s'il était possible d'en allouer par bloc de 1000 ou plus en une seule fois et de les instancier au travers d'un pattern factory par ex ?

    Ha oui en fait mes TAttributs utilisent les interfaces ainsi que le comptage de références mais ça doit pas changer grand chose au problème.

    merci

    XP - Delphi 7

  2. #2
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 736
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 736
    Points : 25 645
    Points
    25 645
    Par défaut
    Tu fais un système de persistante, tu devrais voir si InstantObjects ne répond pas à ton problème ... Sinon regarde autour des RTTI, pour mapper tes tables avec des objets, ... je l'ai déjà fait, c'est un sacré travail, faut d'ailleurs que je la ré-écrive pour la rendre indépendante de la lib de mon employeur, et ainsi la rendre compatible D2009 ... (c'est la lib interne qui la rend pour le moment non compatible)

    Pour l'allocation en masse, à part une boucle, ... pas d'allocation en masse

    Sinon, tu t'aperceveras que tout chargé, c'est une erreur, cela prendre du temps (ton problème justement), des ressources, la couche de persistance précédemment développé dans ma boite était comme ça, et c'est lourd pour les listings ! J'ai donc réécrit une Peristance sur le principe du LazyLoading qui ne charge que le minimum, ... cela charge par exemple, une liste d'ID, puis lorsque tu sollicites dans la collection une instance, cela lance la requête pour lire l'enregistrement entier

  3. #3
    Membre expérimenté

    Homme Profil pro
    Inscrit en
    Mars 2004
    Messages
    897
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2004
    Messages : 897
    Points : 1 561
    Points
    1 561
    Par défaut
    Je confirme que tout charger en mémoire n'est pas une bonne solution. J'utilise quotidiennement les InstantObjects, et je me suis également trouvé confronté au problème de manipuler des tables de plusieurs milliers de lignes ce qui n'est pourtant pas très volumineux. Cela prend pas mal de temps, même en désactivant les options de création automatique d'une instance pour un enregistement pour les composants InstanExposer (foObjects, et foThorough à False).
    Pour contourner ce problème, j'ai mis en place une solution qui ressemble à celle que nous a exposé ShaileTroll. J'utilise tout bêtement un ADOQuery pour lire une table volumineuse ou non, puis je crée les instances des enregistements dès que j'en ai besoin. Chaque enregistrement dispose d'un GUID, le rendant ainsi unique et les instantObjects proposent pour chaque classe une fonction retrieve qui permet d'instancier un enregistrement à partir de son GUID.
    De toute façon, il est rare que lors d'un traitement on ait réellement besoin d'utiliser tous les enregistrements d'une table. Et si ce n'est que pour l'afficher dans une grille. Pourquoi alors créer une instance pour chaque enregistrement dans ce cas ?
    En revanche, dans la couche métier l'instanciation des enregistrements simplifie l'écriture des traitements. Dans cette couche, on manipule qu'un nombre restreint d'objets en général.

  4. #4
    Membre habitué Avatar de phplive
    Profil pro
    Inscrit en
    Avril 2003
    Messages
    179
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2003
    Messages : 179
    Points : 150
    Points
    150
    Par défaut
    Bonjour

    Et merci pour vos réponses.

    C'est rassurant de voir que d'autres arrivent à la même conclusion : les objets c'est bien mais c'est ... lent !
    Enfin, lent, tout est relatif.

    J'ai effectivement jetté un coup d'oeil sur InstantObject en fait je m'en inspire, en bcp plus simple, mais chut ! lol !

    Naturellement je ne compte pas précharger le contenu de toutes mes tables dans des objets Delphi : pourquoi faire en effet ?


    Je me contente de :

    1 Chargement de certains objets depuis un espace de stockage
    2 L'affichage de mes objets en utilisant les composants standard de la VCL (mais pas les composants data-aware !)
    au travers d'un MVP (enfin cette partie là est pas encore mise au point T'Oh !)

    Les problèmes commencent à apparaître avec les affichages dans des ListViews : curieusement avec des TreeViews c'est
    beaucoup plus simple. Peut-être aussi parce que mes objets métiers s'organisent nativement sous la forme d'une arborescence.

    Donc pour en revenir à mes allocations d'objet en bloc (je sais j'y tiens) depuis j'ai pas mal fouiné dans le code du TObjet de l'unité system.pas histoire de voir comment Delphi instancie un objet.

    Lors de l'appel au constructeur il n'invoque pas directement la méthode Create() mais NewInstance()
    NewInstance() alloue la mémoire pour l'objet puis appelle InitInstance()
    InitInstance() initialise à zéro le bloc mémoire alloué, la VMT de l'objet ainsi que ses IMT (ce qui est mon cas car tous mes objets gèrent le comptage de références)
    Finalement la méthode Create() est exécutée

    Il apparaît donc qu'il est facile de
    1) gèrer l'allocation de mémoire soit même en surchargeant NewInstance() et FreeInstance()
    2) on peut même s'affranchir de l'appel à la méthode InitInstance si dans le constructeur on initialise aucune variable, on ne créé aucun autre objet (idem pour la méthode AfterConstruction)

    J'ai testé avec un objet que je créé comme modèle et que je recopie ensuite n fois de suite avec move() dans un buffer créé via GetMem()
    Puis un simple transtypage du style
    IMonObjet := TMonObjet( Pointer(Integer(Buffer)+i*TMonObjet.InstanceSize) ); et ça fonctionne l'objet est opérationnel.

    Je vais donc passer à des tests à grande échelle histoire de voir si je gagne en performance.

  5. #5
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 736
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 736
    Points : 25 645
    Points
    25 645
    Par défaut
    As tu pensé au BeginUpdate et EndUpdate de la TListView (aussi pour le TreeView) qui accélère grandement le remplissage en masse !

    Lorsque j'ai fait ma couche de persistance objet, j'ai utilisé le DBGrid qui pointent sur une Collection de la couche, la collection n'étant que l'encapsulation d'un DataSet), la DBGrid est évidemment en ReadOnly et un formulaire avec un controlleur avec se charger de mapper chaque propriété à un control (non-DBWare aussi),

    J'aurais du ensuite faire en sorte que l'objet envoie une notification à sa collection pour lui dire de remplacer ces données (le dataset doit être uniquement en mémoire genre TClientDataSet ou TMemoryDataSet), à partir de celle de l'objet pour que l'affichage soit à jour (en fait, je relance pas carrément la requête ), pense que tu devras aussi gérer des notifications si l'instance affiché a été changé ...

    Ensuite, surcharger InitInstance, bon courage, ça doit être un sacré boulot, ... commence d'abord par voir si c'est vraiement ça qui bloque, ... moi ça me surprendrait surtout si te ne charge pas 30 000 objets avec chacun 30 propriétés mais juste une petite liste ...

    Ton objet est opérationnel, mais si tu copie la zone mémoire, finalement, tous les objets pointent sur les mêmes choses, et si tu as des sous-objets ou des pointeurs, des tableau dynamique, des strings, tu as recopié l'adresse de tous ces pointeur, mais pas le contenu, as-tu pensé à les réallouer ... car sinon, une modification sur un "objet" modifiera le contenu d'une autre instance, ...

    Tu devrais mesurer les temps de chaque tache avec QueryPerformanceCounter, pour savoir où est vraiement la lenteur

  6. #6
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 845
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 845
    Points : 13 622
    Points
    13 622
    Par défaut
    Pourquoi ne pas essayer un TStringStream.ReadComponent
    Tu crées une simple chaîne formatée (style DFM) avec les infos de ta BD.

  7. #7
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 736
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 736
    Points : 25 645
    Points
    25 645
    Par défaut
    Mais comme cela va lu allouer plusieurs objets à la fois ? tu passerais par une DFM qui contient un Owner puis plein d'objet possédé ? donc finalement, une boucle qui génère une chaine, donc finalement ça c'est plus rapide de faire une boucle qui instancie directement non ?

    J'ai aussi tenté d'utiliser une transformation de la DB en chaine vers une DFM, cela fonctionne, mais c'est bien plus lent que les RTTI ... c'est un code obselète, mais je l'ai gardé pour la nostalgie, ... Et en plus cela me gérait très mal la nullité, je suis vite passé en RTTI qui est plus fiable (les données peuvent contenir des éléments à corriger sinon le Stream fait n'importe quoi)

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    {* -----------------------------------------------------------------------------
    La Fonction DataSetToString prend dans un DataSet, et construit chaine qui contiendra les binomes names=values
    @param DataSet contenant une ligne de données
    @param Value Chaine qui contiendra la version Texte de la DataBase (names=values)
    @return Renvoie un True on a pu extraire les Données
    ------------------------------------------------------------------------------ }
    class function TEpcPersistantDataConverter.DataSetToString(const DataSet: TDataSet; out Value: string): Boolean;
    var
      iField: Integer;
      FieldName, FieldValue: string;
      List: TStrings;
      Field: TField;
      UnQuoteEnd: Boolean;
    begin
      List := TStringList.Create();
      try
        Result := Assigned(DataSet) and not DataSet.IsEmpty;
        if Result then
        begin
          // Le Premier Champ c'est l'ID ... on commence à 1 au lieu de 0 !
          for iField := 1 to DataSet.FieldCount - 1 do
          begin
            Field := DataSet.Fields[iField];
            FieldName := Field.FieldName;
            if not Field.IsNull then
            begin
              case Field.DataType of
                ftBoolean :
                  FieldValue := BoolToStr(Field.AsBoolean, True);
     
                ftSmallint :
                  FieldValue := BoolToStr(LongBool(Field.AsInteger), True);
     
                ftInteger, ftWord, ftLargeint, ftAutoInc :
                  FieldValue := Field.AsString;
     
                ftFloat, ftCurrency, ftBCD :
                  FieldValue := LocaleStringFloatToStringComponent(Field.AsString);
     
                ftDate, ftTime, ftDateTime :
                  FieldValue := DateTimeToStringComponent(Field.AsDateTime);
     
                ftString, ftMemo : begin
                  FieldValue := Field.AsString;
                  UnQuoteEnd := (Length(FieldValue) > 0) and ((FieldValue[Length(FieldValue)] = #10) or (FieldValue[Length(FieldValue)] = #13));
                  FieldValue := AnsiReplaceStr(FieldValue, CRLF, QCRLFQ);
                  if UnQuoteEnd then
                    FieldValue := ''''+Copy(FieldValue, 1, Length(FieldValue) - 1)
                  else
                    FieldValue := ''''+FieldValue+'''';
                end
                else
                  raise EPropertyConvertError.CreateFmt(ERR_DATA_TYPE_NO_MANAGE, [FieldName, FieldTypeNames[Field.DataType]]);
              end;
     
              List.Add(Format('%s=%s', [FieldName, FieldValue]));
            end;
          end;
        end;
     
        Value := List.Text;
      finally
        List.Free();
      end;
    end;
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    class function TEpcPersistantStreamWrapper.DataStringToComponentString(const Value: string; DefaultClass: TEpcPersistantClass): string;
    const
      MAX_LINE_LENGTH = 4096 - Length(#13#10); // cf ObjectTextToBinary ("TWriter.Create(Output, 4096);")
    var
      PersistantClassName, PropertyName: string;
      Index, LLVLength, iLLV: Integer;
      PCD: TEpcPersistantClassDescriptor;
      LongLineValue: string;
      WithStrings: TStrings;
    begin
      Result := '';
     
      // Sur le principe des sous-fonctions ObjectBinaryToText.ConvertHeader et ObjectBinaryToText.ConvertObject
      WithStrings := TStringList.Create();
      with WithStrings do
      begin
        try
          Text := Value;
     
          // Le Nom de la Classe est dans le Champ PersistantClassName
          Index := IndexOfName(FIELD_CLASS_NAME);
          if Index >= 0 then
          begin
            if Index <> 0 then
            begin
               PersistantClassName := Strings[Index];
               Delete(Index);
               Insert(0, PersistantClassName);
               Index := 0;
            end;
     
            PersistantClassName := DequotedStr(Values[Names[Index]], True);
            if Trim(PersistantClassName) <> '' then
            begin
              Strings[Index] := Format('object %s', [PersistantClassName]);
              PCD := TEpcPersistantClassManager.Manager.PersistantClassDescriptorsByName[PersistantClassName];
            end else
              raise EListError.Create(ERR_NO_CLASS_NAME);
          end else
          begin
            if Assigned(DefaultClass) then
            begin
              PersistantClassName := DefaultClass.ClassName;
              Insert(0, Format('object %s', [PersistantClassName]));
              PCD := TEpcPersistantClassManager.Manager.PersistantClassDescriptorsByClass[DefaultClass];
            end else
              raise EListError.CreateFmt(ERR_DATA_STRING_INCORRECT, [Value, ERR_NO_CLASS_NAME]);
          end;
     
          if Assigned(PCD) then
          begin
            // Elimination des Champs DB non présents dans la Classe Persistante ! Est-ce Possible ???
            // Le Dernier est la Classe et non une propriété !
            for Index := Count - 1 downto 1 do
            begin
              PropertyName := Names[Index];
              // Le Champ est-il lié à une Propriété ?
              if (Trim(PropertyName) <> '') and (PCD.Properties.IndexOf(PropertyName) < 0) then
                Delete(Index)
              else begin
                if Length(Strings[Index]) < MAX_LINE_LENGTH then
                begin
                   ValueFromIndex[Index] := EncodeCharSet(ValueFromIndex[Index]);
                end else
                begin
                  LongLineValue := Strings[Index];
                  Strings[Index] := '';
     
                  LLVLength := Length(LongLineValue);
                  iLLV := 0;
                  while LLVLength > 0 do
                  begin
                    Insert(Index+1, EncodeCharSet(Copy(LongLineValue, iLLV*64+1, 64)));
                    Dec(LLVLength, LineLength);
                  end;
                end;
              end;
            end;
     
            Add('end'); // Fin de l'Objet
            Result := Text;
          end else
          begin
            raise EClassNotFound.CreateFmt(SClassNotFound, [Format('Persistante %s', [PersistantClassName])]);
          end;
        finally
          Free();
        end;
      end;
    end;
    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
    {* -----------------------------------------------------------------------------
    La Fonction StringToComponent prend dans une String, le Component et ses propriétés publiées dans le format de Flux Delphi (cf dfm)
    @param Value est une chaine contenant la version Texte du Component
    @return Contient l'Instance héritée de TComponent instancié via le Flux
    ------------------------------------------------------------------------------ }
    class function TEpcPersistantStreamWrapper.StringToComponent(const Value: string): TComponent;
    var
      StrStream:TStringStream;
      BinStream: TMemoryStream;
    begin
      if Value = '' then
      begin
        Result := nil;
        Exit;
      end;
     
      StrStream := TStringStream.Create(Value);
      try
        BinStream := TMemoryStream.Create;
        try
          ObjectTextToBinary(StrStream, BinStream);
          BinStream.Seek(0, soFromBeginning);
          Result := BinStream.ReadComponent(nil);
        finally
          BinStream.Free;
        end;
      finally
        StrStream.Free;
      end;
    end;

  8. #8
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 845
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 845
    Points : 13 622
    Points
    13 622
    Par défaut
    J'admais que mes tests avec ReadComponent datent de D1

    Par contre, les rapidité et fiabilité, on peut se poser la question
    - un ensemble de structures connues.
    - un seul chargement (un minimum d'allocation).
    - Des propriétés ignorées ou à venir qui sont initialisées à leurs valeurs par défaut si non-définies.

    La seule boucle est le FindNext dans la BD qui aura de toute façon lieu.
    De plus la root peut être n'importe quel composant. On se limite au strict minimum.

  9. #9
    Expert confirmé

    Profil pro
    Leader Technique
    Inscrit en
    Juin 2005
    Messages
    1 756
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France, Rhône (Rhône Alpes)

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 1 756
    Points : 4 173
    Points
    4 173
    Par défaut
    Citation Envoyé par phplive Voir le message
    Le problème provenant spécifiquement de l'allocation des attributs je me demandais s'il était possible d'en allouer par bloc de 1000 ou plus en une seule fois et de les instancier au travers d'un pattern factory par ex ?
    Il faudrait commencer par identifier précisément la raison des lenteurs. En particulier, s'il s'agit de l'allocation mémoire en elle-même ou ensuite de l'initialisation de l'instance.
    Si c'est l'allocation mémoire, je te conseillerais déjà de changer de gestionnaire de mémoire. Tu pourrais utiliser FastMM4. D'ailleurs maintenant, c'est le nouveau gestionnaire de Delphi depuis BDS2006.
    Mais, je pense que c'est plutôt l'initialisation autour qui doit te prendre le plus de temps.

    C'est rassurant de voir que d'autres arrivent à la même conclusion : les objets c'est bien mais c'est ... lent !
    Enfin, lent, tout est relatif.
    C'est vrai. Mais en même temps soyons honnête ! Les outils de mapping O/R et autres frameworks de persistances n'ont jamais été réputés pour leurs performances.
    Pour moi, ils ne se justifient que si c'est pour monter au dessus de la base un modèle objet radicalement différent de la structure des tables. Sinon, on dégrade considérablement les performances pour pas grand chose (à si pardon, pour ne pas montrer qu'on ne sait pas écrire une requête SQL).

    Je reste persuadé que le meilleur compromis entre la souplesse d'utilisation et les performances serait une architecture avec des services métiers d'un côté (et donc uniquement du code) qui travaillent sur des data-objects (en gros des records typés, structurés correspondant aux enregistrements des tables).
    Les data-objects sont générés plus ou moins automatiquement avec un générateur de code, à partir de la structure de la base. On peut ainsi les creer directements en lots avec des API bas niveau tel que OLEDB ou OCI, en s'affranchissant complètement de l'architecture DataSet de Delphi.
    Puis les services métiers présentent une interface objet sur ces structures et implémentent la logique métier...

    J'ai donc réécrit une Peristance sur le principe du LazyLoading qui ne charge que le minimum, ... cela charge par exemple, une liste d'ID, puis lorsque tu sollicites dans la collection une instance, cela lance la requête pour lire l'enregistrement entier
    C'est marrant, je fais exactement le contraire : J'essaie de précharger un maximum de données au début des traitements pour minimiser ensuite le nombre de requêtes nécessaires.
    Tu obtiens de meilleurs performances si tu exécutes une requête qui te retourne les 1000 enregistrements dont tu vas avoir besoin que si tu exécutes ta requête 1000 fois en ne recupérant qu'un seul enregistrement à la fois (surtout avec Delphi et les DataSets).

  10. #10
    Expert confirmé

    Profil pro
    Leader Technique
    Inscrit en
    Juin 2005
    Messages
    1 756
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France, Rhône (Rhône Alpes)

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 1 756
    Points : 4 173
    Points
    4 173
    Par défaut
    Citation Envoyé par Andnotor Voir le message
    J'admais que mes tests avec ReadComponent datent de D1

    Par contre, les rapidité et fiabilité, on peut se poser la question
    - un ensemble de structures connues.
    - un seul chargement (un minimum d'allocation).
    - Des propriétés ignorées ou à venir qui sont initialisées à leurs valeurs par défaut si non-définies.

    La seule boucle est le FindNext dans la BD qui aura de toute façon lieu.
    De plus la root peut être n'importe quel composant. On se limite au strict minimum.
    Tu dis ça parce que tu n'as pas conscience de tout ce que Delphi fait à ta place :
    Des boucles, il y en a beaucoup plus que ce que tu crois :
    - D'abord, celle inévitable : La lecture des enregsitrements de la base. Sauf que dans cette dernière, on va encoder un gigantesque string. Comme on ne connait pas la taille du string à priori, on va passer notre temps à allouer/étendre des buffers toujours plus grand pour construire la chaîne finale. De plus, durant cette boucle, on prends les données binaires qui proviennent de la base, pour les étendres au format textuel. On fait donc une première copie des données lues dans la base.
    - Ensuite vient le chargement des objets à partir de la chaîne. Il n'y a pas de miracle, on ne peut pas deviner comme ça les objets décrits dans la chaîne. Il va falloir la parser, donc la lire caractères par caractères, pour en extraire des identificateurs.
    - Lorsque tu as identifié un identificateur, il faut encore savoir ce qu'on doit faire. Prenons un cas simple, la chaîne se contente de décrire les proriétés d'un seul et unique objet. L'identificateur qu'on a trouvé correspond donc au nom d'une propriété publiée de l'objet. Il faut alors rechercher la propriété qui porte le même nom que celui indiqué par l'identificateur.
    - A ce moment, les RTTI ne t'ont fournis qu'un simple setter. C'est à dire, un pointeur sur une méthode que tu pourras appeler pour écrire la valeur de la propriété.
    - Mais on ne connait toujours pas la valeur à écrire. Donc il faut continuer à parser le string du départ, pour pouvoir désérialiser la valeur de la propriété.
    - Ce n'est qu'à ce moment là qu'on retrouve la valeur binaire qu'on connaissait déjà au moment de la lecture de la base. Il reste à exécuter le setter avec cette valeur pour initialiser la propriété de l'objet.

    C'est la VCL qui fait tout ça automatiquement, donc on ne le voit pas. Mais le traitement est bel et bien présent. Si on construit directement l'objet à partir de la base, on s'affranchit de tous ces traitements intermédiaires.
    On peut même écrire des DAO statiques et charger l'objet sans avoir recours aux RTTI... (par contre ensuite il faut maintenir les DAO...).

  11. #11
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 736
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 736
    Points : 25 645
    Points
    25 645
    Par défaut
    Citation Envoyé par Franck SORIANO Voir le message
    C'est marrant, je fais exactement le contraire : J'essaie de précharger un maximum de données au début des traitements pour minimiser ensuite le nombre de requêtes nécessaires.
    Tu obtiens de meilleurs performances si tu exécutes une requête qui te retourne les 1000 enregistrements dont tu vas avoir besoin que si tu exécutes ta requête 1000 fois en ne recupérant qu'un seul enregistrement à la fois (surtout avec Delphi et les DataSets).
    Ah mais j'ai les deux modes ! j'ai un système de collection qui va selon les options charger la requête au complet ou juste la liste des ID, ... et attention, il y a le LazingLoading pour la collection (ID ou *) mais tu peux aussi avoir le LazingLoading pour l'instance, je rempli l'instance de la collection qu'au moment où je vais l'utiliser, je ne lance pas de requête, je recopie le dataset interne de la collection (je relance une requête si il n'y a que l'ID), donc une seule requête SQL mais RTTI différé !

    Et donc pour les traitements de boucle, je peux appeler la collection via la méthode First qui fonctionne selon l'option général, ou alors la méthode Load qui supplentera l'option général pour tout charger. !

    Comme le dit Franck SORIANO, Pour les références publiées il y a FieldAdress qui permet le mapping, ... il y a des choses très interressantes aussi avec le Owner quand tu le retire, cela joue sur les références ... encore des trucs implicites !

    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
    procedure ChangeOwner(var AComponent: TComponent; AOwner: TComponent);
      var
        SaveComponent: TComponent;
        Field: ^TComponent;
        I: Integer;
      begin
        if Assigned(AComponent) and Assigned(AComponent.Owner) and Assigned(AOwner) then
        begin
          if AComponent.Owner <> AOwner then
          begin
            SaveComponent := AComponent; // Mémorise la référence avant qu'elle soit mis à nil
            AComponent.Owner.RemoveComponent(AComponent); // RemoveComponent mets la référence publiée de AComponent dans son Owner a nil !
            AOwner.InsertComponent(SaveComponent);
            AComponent := SaveComponent;
     
            if AComponent is TWinControl then
              for I := 0 to TWinControl(AComponent).ControlCount - 1 do
              begin
                SaveComponent := TComponent(TWinControl(AComponent).Controls[I]);
                Field := SaveComponent.Owner.FieldAddress(SaveComponent.Name);
                ChangeOwner(SaveComponent, AOwner);
                if Field <> nil then
                  Field^ := SaveComponent;
              end;
          end;
        end;
      end;

  12. #12
    Membre habitué Avatar de phplive
    Profil pro
    Inscrit en
    Avril 2003
    Messages
    179
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2003
    Messages : 179
    Points : 150
    Points
    150
    Par défaut
    Bjr à tous

    Haha je vois que le persistent framework fait débat !

    Ha si seulement nos bases de données étaient objet ...

    Ha tient ça existe (en open source s'entend) : EyeDB quelqu'un connait ?


    C'est dingue le nombre de choses qu'il faut écrire rien que pour faire un modeste petit "framework" pour gérer des objets plutôt que ces fichus appels SQL direct ! Une gestion de listes, de cache, de messages, des algos de tri, de recherche, un générateur SQL et j'en passe ...

    Heureusement les interfaces me permettent de gèrer facilement la libération de tous mes objets (je suis sous D7 donc pas Garbage Collector )

    Afin de m'affranchir des problèmes de relative lenteur lorsque le nombre d'objets à créer augmente, j'ai commencé à tester les Listviews en mode virtuel : j'associe une liste d'objets métiers à ma LV. Ma LV ne connait (et encore pas directement) qu'un seul objet métier "maître" : la liste métier qui constitue en quelque sorte un point d'entrée dans le modèle.
    C'est cette liste métier qui indique à la LV ce qu'elle doit afficher. La liste métier, elle, reste reliée à un TQuery (pas directement en fait mais peu importe) et agit comme un sorte de curseur en chargeant dynamiquement les données de chaque enregistrement du TQuery dans 1 à N objets. Comme ce sont tjrs les mêmes objets ça va vite La liste métier reste donc vide tant qu'on ne sélectionne pas explicitement un objet auquel cas il est ajouté à la liste métier.

    Inconvénient : je conserve en permanence un connexion au TQuery et à la base de données.
    La LV attend un nombre d'items explicite.

    Pour le moment je n'ai pas encore trouver de moyen pour rendre les composants de la VCL "business object aware" comme dirait JCVD.
    Je pensais que ce serait la partie la plus facile hé ben non

  13. #13
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 736
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 736
    Points : 25 645
    Points
    25 645
    Par défaut
    Citation Envoyé par phplive Voir le message
    Heureusement les interfaces me permettent de gèrer facilement la libération de tous mes objets (je suis sous D7 donc pas Garbage Collector )
    Moi, j'ai tout basé sur un owner commun et la gestion d'un owner secondaire, les collections étant propriétaires des objets instanciés (sauf si demande d'extraction), et donc libère leur objet à leur propre libération, donc si l'on a un objet A, qui à une relation 0-N avec B, et B 0-C à avec C, si tu parcours tous les objets dans une boucle via le framework, libéré A, libère tous les petits B et C ... c'est tellement plus intéressant de maîtriser la mémoire, moi je trouve que c'est un aspect passionnant de la programmation, et oblige à un minimum de rigueur, ... qui se perd avec le GC ...

    Ensuite, quand tu as un DBGrid, la connexion Query reste ouverte, cela ne fait pas grande différence non ? Mets toutes les options que ton Query soit le moins gourmand (ReadOnly, Cache, ...)

    Pour faire une sorte ObjetSource (reprenant le DataSource), oui, c'est prise de tête, au bureau on a en fait un, j'ai du reprendre le code au moins une bonne dizaine de fois, et c'est moche moche moche dedans (toute ma persistance utilise du variants, et de plus le MappeurUI devait être compatible avec l'ancienne couche persistante, et aussi capable de gérer du DataSouce) ... sinon l'interface, ça va, pas top, mais bon ...
    MappeurUI intégré en ComponentEditor de Delphi

    En base Objet, tu as O2 (DB Orsay)

    Je vais être lourd, tu as testé BeginUpdate et EndUpdate ? Peux tu donner des ordres de temps pour 100, 1000 et 10000 enreg pour que se fasse un idée ce que tu considères comme lent ? 100ms ? 1s ? 10s ? 1minute ???

  14. #14
    Expert confirmé

    Profil pro
    Leader Technique
    Inscrit en
    Juin 2005
    Messages
    1 756
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France, Rhône (Rhône Alpes)

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 1 756
    Points : 4 173
    Points
    4 173
    Par défaut
    Citation Envoyé par phplive Voir le message
    Ha si seulement nos bases de données étaient objet ...
    Comme l'as fait remarquer ShaiLeTroll. Ca existe, et depuis longtemps.
    Sauf que dans la pratique, elles n'ont pas réussi à séduire le marché (beaucoup trop lentes il me semble, mais je n'ai jamais essayé).

    Heureusement les interfaces me permettent de gèrer facilement la libération de tous mes objets (je suis sous D7 donc pas Garbage Collector )
    Fait attention. Les interfaces fonctionnent par comptage de références. Ce qui a pour effet que dès que tu as un cycle dans tes références (L'objet A est en relation avec B. Il possède une référence sur B. B est en relation avec C, qui a son tour est en relation avec A...), plus rien n'est libéré, même si tu ne possède plus aucune référénce sur le cycle (les vrais GC n'ont pas cette limitation).

    Afin de m'affranchir des problèmes de relative lenteur lorsque le nombre d'objets à créer augmente, j'ai commencé à tester les Listviews en mode virtuel : j'associe une liste d'objets métiers à ma LV. Ma LV ne connait (et encore pas directement) qu'un seul objet métier "maître" : la liste métier qui constitue en quelque sorte un point d'entrée dans le modèle.
    C'est cette liste métier qui indique à la LV ce qu'elle doit afficher. La liste métier, elle, reste reliée à un TQuery (pas directement en fait mais peu importe) et agit comme un sorte de curseur en chargeant dynamiquement les données de chaque enregistrement du TQuery dans 1 à N objets. Comme ce sont tjrs les mêmes objets ça va vite La liste métier reste donc vide tant qu'on ne sélectionne pas explicitement un objet auquel cas il est ajouté à la liste métier.
    Ca a l'air pas mal. Nous on fonctionne un peu sur le même principe : Nos objets métier sont en réalité une classe d'adaptation sur un dataset. Ils offrent une vue objet de l'enregistrement courant du dataset.
    Ca revient un peut au même, sauf que notre objet est à la fois un objet et une liste, et qu'on ne conserve en mémoire qu'une seule instance de l'objet, les données étant stockées dans le dataset.
    L'avantage c'est que selon nos besoins, on peut manipuler soit l'objet de gestion, soit directement le dataset.
    Par contre, ce n'est pas vraiment un modèle objet, mais c'est suffisant pour nos besoins.

    Pour le moment je n'ai pas encore trouver de moyen pour rendre les composants de la VCL "business object aware" comme dirait JCVD.
    Je pensais que ce serait la partie la plus facile hé ben non
    Une solution relativement simple consiste à passer par une classe d'adaptation que tu définis en créant un dataset personnalité (tu dérives la classe TDataset). Cette dernière se présente alors comme un dataset. Tu peux brancher un DataSource dessus. Par contre, lorsque le dataset se déplace d'une ligne à une autre, tu lui fait lire les données de ta liste d'objets métier. Lorsque le dataset lit un TField, tu lui fait lire un champ de l'objet métier... En écriture, tu mets à jour les champs de ton objet au moment du Post du dataset...
    J'ai un tuto en cours sur le fonctionnement du TDataSet.

  15. #15
    Membre habitué Avatar de phplive
    Profil pro
    Inscrit en
    Avril 2003
    Messages
    179
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2003
    Messages : 179
    Points : 150
    Points
    150
    Par défaut
    Bsr

    Hum vous proprosez tous des idées intéressantes dificile de choisir

    Oui j'utilise beginupdate/endupdate avec tous les composants qui possèdent ces méthodes : c'est clair que ca accélère un max

    Lent ?
    Hum sachant qu'au final j'ai des utilisateurs peu enclin à la patience je dirais est lent tout ce qui n'est pas perçu comme instantanné ou qui entraîne un clic rageur plusieurs fois de suite au même endroit dans l'espoir d'accélérer les choses

    personnellement
    1 min pour 10000 enreg c'est hors de question !
    10" c'est lent
    1" c'est correct
    en 100eme ou en 1000eme : idéal mais tout le monde n'a pas un Dual Core à 3GHz

    Pour l'utilisateur une seconde ça fini de toute façon par se traduire par un "mais qu'est ce qu'il rame ton prog !"


    Les interfaces : moi aussi au début j'étais réticent. C'est plus lourd (voir plus lent un QueryInterface ça coûte), de plus on ne sait jamais quelles sont les interfaces implémentées par un objet => faut documenter !
    Mais depuis j'ai changé d'avis et surtout j'ai définitivement adopté le comptage de références : les strings sous Delphi sont d'ailleurs gérées ainsi et tout va pour le mieux.
    Bien sur on va me dire les interfaces gaffe, les références circulaires danger !


    C'est vrai mais avec les objets classiques je ne compte plus le nombre de fois où un objet était détruit trop tôt et dans des situations plutôt délicates à tracer même au débogueur.

    Avec les interfaces c'est l'inverse : on risque de ne pas libérer certains objets voir certaines chaînes d'objets dès qu'on a une structure qui prend la forme d'un graphe.

    Pour parer à ce problème j'utilise des weaks références pour caser le références circulaires explicites.
    Ensuite comme j'ai créé ma propre classe d'objet interfacé je compte le nombre d'objets crées et libérés ainsi je peux detecter si des objets persistent en mémoire. Je peux faire la distinction par type de classe. Ca permet de voir quels sont les objets en cause.
    Enfin lorsque j'ai un doute tous mes objets implémentant par défaut le visitor pattern je parcours tous mes objets jusqu'à tomber sur un cycle

  16. #16
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 736
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 736
    Points : 25 645
    Points
    25 645
    Par défaut
    J'ai aussi quelques traitements lents comme l'affichage d'une fenêtre via un XML transformé à volé en DFM via un XSL, et tout plein de manipulation via DelphiScript, eh bien, cela doit mettre entre 1 et 4 secondes, pour éviter le clic rageur, j'affiche une petite fenêtre de progression qui indique chaque étape du traitement : Récupération Données, Récupération Profil, Récupération Feuille de Style, Transformation, Fusion Donnée Profil, ... ainsi l'utilisateur a une réaction instantanée de l'application ... il ne s'énerve pas, il rale parce que c'est lent, et le boulot est de réduire le process, ce qu'il se rend compte lorsque la nouvelle version affiche une barre de progression quasi-invisible (on l'a retire pas car chez certains clients le SQL prend plus de temps, ou parce qu'ils ont plus de script ou que le profil d'écran est énorme)

    Ce qui je fais aussi c'est désactiver le bouton (voire la fenêtre entière pour éviter toute colision) en prévision d'un application.processmessage qui viendrait à accepter un clic furieux

    Sinon pour les interfaces, ce t'oblige soit d'hériter un TInterfacedObjet, soit de toi même reproduire le comportement de compteur de référence ? non ? Je n'ai jamais fait d'interface en dehors de l'utilisation en Automation ! Donc je ne suis pas très au clair ... Pour les relations triplettes A, B, C, j'en ai lors d'une association N-N via Objet (A et C sont N-N via la Relation B)
    Moi mes objets héritent du TComponent pour le RTTI (TPersistent) et pour le Owner (et les notifications associées)

    Tient, pour rigoler dans mon Application de Test de la Couche, Relation A-B via Z, avec évidemment des collections qui se chevauchent ...

    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
    procedure TFrmRelationAZB.BtnReadRelationClick(Sender: TObject);
    var
      ObjAAA: TepcObjectAAA;
      ObjBBB: TepcObjectBBB;
      ObjZZZ: TepcObjectZZZ;
    begin
      MemoLoad.Lines.Clear();
     
      ObjAAA := TepcObjectAAA.Load(LastID_AAA) as TepcObjectAAA;
      ObjBBB := TepcObjectBBB.Load(LastID_BBB) as TepcObjectBBB;
      ObjZZZ := TepcObjectZZZ.Load(LastID_ZZZ) as TepcObjectZZZ;
     
      if not Assigned(ObjAAA) then begin MemoLoad.Lines.Add('ObjAAA : Not Found !'); Exit; end;
      if not Assigned(ObjBBB) then begin MemoLoad.Lines.Add('ObjBBB : Not Found !'); Exit; end;
      if not Assigned(ObjZZZ) then begin MemoLoad.Lines.Add('ObjZZZ : Not Found !'); Exit; end;
     
      DBGridAB.DataSource := ObjAAA.BBB.Load().DataSource;
      DBGridAZ.DataSource := ObjAAA.ZZZ.Load().DataSource;
      DBGridBA.DataSource := ObjBBB.AAA.Load().DataSource;
      DBGridBZ.DataSource := ObjBBB.ZZZ.Load().DataSource;
      DBGridZA.DataSource := ObjZZZ.AAA.Load().DataSource;
      DBGridZB.DataSource := ObjZZZ.BBB.Load().DataSource;
     
      if not ObjAAA.BBB.IsEmpty then LoadAll('ObjAAA.BBB : ', ObjAAA.BBB) else MemoLoad.Lines.Add('ObjAAA.BBB : Not Found !');
      if not ObjAAA.ZZZ.IsEmpty then LoadAll('ObjAAA.ZZZ : ', ObjAAA.ZZZ) else MemoLoad.Lines.Add('ObjAAA.ZZZ : Not Found !');
      if not ObjBBB.AAA.IsEmpty then LoadAll('ObjBBB.AAA : ', ObjBBB.AAA) else MemoLoad.Lines.Add('ObjBBB.AAA : Not Found !');
      if not ObjBBB.ZZZ.IsEmpty then LoadAll('ObjBBB.ZZZ : ', ObjBBB.ZZZ) else MemoLoad.Lines.Add('ObjBBB.ZZZ : Not Found !');
      if not ObjZZZ.AAA.IsEmpty then LoadAll('ObjZZZ.AAA : ', ObjZZZ.AAA) else MemoLoad.Lines.Add('ObjZZZ.AAA : Not Found !');
      if not ObjZZZ.BBB.IsEmpty then LoadAll('ObjZZZ.BBB : ', ObjZZZ.BBB) else MemoLoad.Lines.Add('ObjZZZ.BBB : Not Found !');
    end;
    je laisse imaginer que l'on peut écrire cette horreur

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
      (((ObjAAA.BBB.Items[I] as TepcObjectBBB).ZZZ.Items[K] as TepcObjectZZZ).AAA.First();
    par défaut je ne surcharge pas Items donc j'ai un TepcPersistant l'ancêtre, qui oblige le transtypage, si quelqu'un avait une solution à ce sujet, à part les générique, je suis en D7
    Ces sujets sont à lire :
    Avis sur conception de classe et IHM
    [BDD] Séparation de couche de données
    Comment concevez-vous vos objets ?

  17. #17
    Membre habitué Avatar de phplive
    Profil pro
    Inscrit en
    Avril 2003
    Messages
    179
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2003
    Messages : 179
    Points : 150
    Points
    150
    Par défaut
    Salut,

    Sinon pour les interfaces, ce t'oblige soit d'hériter un TInterfacedObjet, soit de toi même reproduire le comportement de compteur de référence ? non ?
    Exact ! En fait j'ai effectivement reproduit l'équivalent du TInterfacedObjet à cause d'une erreur (un oubli ?) dans le TInterfacedObject._Release
    En effet si par ex tu surcharges Destroy à dés fins de débogage et que tu utilises une interface sur l'objet en cours de destruction, par ex en passant l'interface en tant que paramètre à une fonction, au retour le compteur de référence est décrémenté (normal) et la procédure Destroy est de nouveau appellée ! La pile supporte ça ... moyennement

    En plus comme je voulais faire un décompte des appelles à create/destroy j'étais obligé. En fait j'ai un singleton ObjectManager de classe TObjectManager que tous mes objets de type TCoreObject (c'est mon équivalent de TInterfacedObject) appellent


    La barre de progression c'est encore le mieux tu as raison : au moins l'utilisateur sais que le programme tourne toujours. Quand je dis l'utilisateur je m'inclus aussi : j'aime bien avoir la certitude que le programme ne tourne pas en rond. Surtout que si tu utilises beginupdate/endupdate ben l'interface est pas vraiment actualisée


    if not Assigned(ObjAAA) then begin MemoLoad.Lines.Add('ObjAAA : Not Found !'); Exit; end;
    hé ho y'a qu'avec les interfaces que tu peux te permettre ce genre de sortie extrême Je suppose que c'est juste un test ... et que d'habitude tu utilises un try... finally pour libérer correctement tes objets.


    par défaut je ne surcharge pas Items donc j'ai un TepcPersistant l'ancêtre, qui oblige le transtypage, si quelqu'un avait une solution à ce sujet, à part les générique, je suis en D7
    Hélas non
    La surcharge des opérateurs mais surtout les templates font cruellement défaut en D7 !
    Donc j'écris une liste spécifique à chaque fois ou je surcharge les types d'éléments contenus. C'est chiant (comparé au C++) mais pas le choix

    Y'a bien la possibilité de faire un bricolage comme ceci
    http://www.dummzeuch.de/delphi/objec...s/english.html
    mais c'est loin d'égalé le passage de types !

    Pas de préprocesseur sous Delphi et ça se voit ...

    j'ai pas encore finalisé la structure de mes objets mais je pense qu'ils vont se comporter comme des noeuds d'une arborescence (mais pas forcément avec la notion de parent, sibling et cie !) Je veux pouvoir descendre dans mon arborescence je pense que c'est suffisant.

    Je pensais aussi faire 2 types d'objets distincts :

    POObject : PersistentObject donc qui vont contenir l'équivalent d'un enregistrement d'une table
    AttributObject : attribut uniquement d'un POObject
    POObject contenant une liste d'attributs
    Ce sont les objets POObject qui seront gérés en cache : chaque objet POObjet devant être unique dans toute l'application. Pas 2 fois le même client en mémoire par ex à un instant T dans le cache.

    BOObject : BusinessObject qui va contenir un lien vers un POObject ainsi qu'une liste pouvant contenir d'autres objets BOObject ou BOObjectList

    Note que je ne prévois pas de POObjectList ...

    Tous mes objets implémentent les patterns visitor/observer : c'est comme ça qu'un POObject sait à quels BOObject il doit notifier
    les attributs eux peuvent au choix notifier soit directement à leur POObject soit utiliser le pattern observer : j'ai pas encore décidé

    Si j'arrive à faire fonctionner tout ça ce sera cool

  18. #18
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 736
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 736
    Points : 25 645
    Points
    25 645
    Par défaut
    Citation Envoyé par phplive Voir le message
    En plus comme je voulais faire un décompte des appelles à create/destroy j'étais obligé. En fait j'ai un singleton ObjectManager de classe TObjectManager que tous mes objets de type TCoreObject (c'est mon équivalent de TInterfacedObject) appellent
    moi aussi, TepcPersistantClassManager (qui gère ClassType et Instances), donc en fait, tu as tellement écrit de truc avec ton singleton, que ça changerait pas grand chose d'enlever l'interface, ... c'est juste que ça libéré à la mise à nil d'une référence (ou au end; d'une procédure)

    Pour mon histoire de sortie, ben, oui c'est un test, j'ai toujours un try finally, J'ADORE les try-finally ... et puis quoi qu'il arrive le ClassManager connait toutes les instances (quoi, pas directement, il connait d'abord les instances créés simplement, puis les collections, ces dernières étant propriétaires des instances, avec de la récursivité on y arrive, c'est juste pour garantir toutes libérations, je ne les parcours jamais (le Owner est la pour ça), j'ai parfois changé le Owner (le Singleton vers la collection par exemple) à la volée, hum, pas très utile puisqu'une collection maintient un DataSet et une FPersistantList interne pour les instances lazyloadées, à simplifier en laissant tout au TEpcPersistantClassManager.PersistantOwner, ça éviterait des notifications, d'ailleurs, j'ai parfois des TList, TObjetList, array sur pointeur, array d'objet, ça dépend du jour, et donc les Relations qui hérite des Collections, elles sont possédées par le Persistant Maitre de la relation, encore un sous niveau ... c'est un peu le bordel finalement !)

    Et pour l'autre truc AAA.BBB.ZZZ.... si je libère AAA (point d'entrée), les collections et les items créés depuis AAA (donc BBB et ZZZ), seront libérés en cascade ... ben finalement c'est plutôt un graphe (relation 1-n et n-n, l'arbre ne suffit plus !)

    Citation Envoyé par phplive Voir le message
    chaque objet POObjet devant être unique dans toute l'application. Pas 2 fois le même client en mémoire par ex à un instant T dans le cache.
    Bon, moi, j'ai pas de notion de cache, je peux créer plein de fois un objet pointant sur le même enregistrement, mais c'est une idée, ainsi lorsque par exemple dans une sous fênêtre on recharge un objet déjà chargé, on le ré-utilise, on le modifie, ainsi pas besoin de recharger l'objet au retour à la fenêtre précédante, hum je vois mieux l'intéret que tu as porté aux interfaces, mais du coup, tu dois ballayer souvent la liste des instances créés, tu m'étonnes que tu as des lenteurs, car à chaque fois que tu crées une instance, tu vérifie la liste si celle-ci n'existe pas déjà (tu as surement un clé genre TableName+TablePrimaryKey ?)

    Ai-je bien compris ? ou pas ?

    Tient, le prototype de ma classe de base TEpcPersistant

    tu y noteras, le sytème de relation, et aussi le Data, l'équivalent de ton POOAttributes (moi c'est un tableau de record avec des variant et TypInfo)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      TEpcPersistant = class(TComponent)
    ...
      protected
        { Méthodes d'Accès pour les Propriétés Indicées }
        function GetData(const Index: Integer): Variant; virtual;
        procedure SetData(const Index: Integer; const Value: Variant); virtual;
        { Propriétés Protégées }
        property Data[const Index : Integer]: Variant read GetData write SetData;
        { Propriétés Protégées à Publier !}
        property Relations[const PersistantClass: TEpcPersistantClass]: TEpcPersistantRelation read GetRelations;
    la notion de relation entre objet (~Table) est présente dès la classe de base,
    en fait, dans le code suivant, j'utilise le type de relation de base, et je n'ai pas eu l'idée de faire une référence sur la classe TEpcPersistantRelation, voilà un travail intéressant, je pourrais très bien, faire un système de RegisterRelation, qui indiquerait au ClassManager que la Relation entre la Class TepcObjectAAA et TepcObjectBBB et la Classe TepcPersistantRelationAAABBB ... hum voilà, une mission, ça me motivera pour démanteler d'autres parties de code obselète ou moche, ... l'artiste revient !

    Je n'avais donc pas penser à faire une Liste spécifie, surtout que le moteur de relation est interne au TepcPersistant de base, bon bientôt je n'aurais plus besoin de transtyper, ! hum comment géré les class de relations sur les hérité de AAA et BBB, hum, à voir, peut-être que là il faudrait rester avec le transtypage !

    Sinon, oh, tu appliques les pattern, c'est bien, moi je fais tout à l'humeur du jour, c'est mon côté Troll !


    As-tu pensé à l'héritage ?



    Lol, tu y bosse depuis 2004, eh ben, tu avais l'air d'être incompris à ce moment, moi j'y ai travaillé qu'à partir de fin 2006 (j'étais plus dans trip mémoire, pointeur, structure, chaine que dans la POO que j'explore depuis en gros 4 ans vraiement, sur 9 ans de delphi) , j'ai ré-écrit une appli en me basant dessu en 2007, et depuis, je maintiens de temps en temps, l'appli, mais mes objets n'évolue pas,

  19. #19
    Membre habitué Avatar de phplive
    Profil pro
    Inscrit en
    Avril 2003
    Messages
    179
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2003
    Messages : 179
    Points : 150
    Points
    150
    Par défaut
    Oui après des tests des tests et encore des tests j'ai fini par me rendre compte qu'un TRelationManager s'imposait !

    ben ouais mes POObject, BOObject je les sors pas de mon chapeau ... j'ai quand même réfléchi un peu à la question

    Dedans j'associe 2 classes entres elles de type POObject (par contre je me moque bien des cardinalités) et je définie leurs attributs communs servant de clés (pour des raisons historiques mes tables ne contiennent pas d'ID unique donc je suis obliger de faire avec les clés existantes )

    Pour être tout à fait franc je construis mon framework un peu à l'envers en partant du TQuery et en remontant vers mes objets métiers et en essayant de généraliser au maximum. Et quand je dis qu'il est indépendant de la méthode de stockage c'est vrai mais je compte de toute façon l'utiliser exclusivement avec des bases de données et du SQL Par contre tous mes objets peuvent fonctionner sans espace de stockage mais dans ce cas ils ne sont plus persistants of course puisqu'on peut plus les enregistrer !

    Le TRelationManager s'imposait donc parce que mon générateur SQL en avait besoin ! Ah nous y voilà ! En effet pour se charger mon TBOObject à besoin au final de générer une requête : pour cela il s'appuie sur un autre objet TMechanism (je sais plus où j'ai piquée cette idée de mécanisme mais elle fonctionne bien) qui est virtuel et qui concrètement est un TBDEMechanism , un TADOMechanism, un TMySQLMechanisme, un TXMLMechanism etc ...

    Pour constuire une requête par ex un SELECT lors du chargement le
    TBDEMechanism commence par constuire une pseudo requête où les noms des tables correspondent aux noms des classes d'objet de type TPOObject et les champs au noms des attributs de type TAttribut ca donne un truc du genre
    SELECT TPOClient.Nom, TPOClient.ID FROM TPOClient WHERE TPOClient.Nom like 'V%'

    Et pour faire ça le mécanisme à besoin que la classe TBOObjet l'aide : en fait
    je résous le problème en utilisant des méthodes de classe pour faire de l'instrospection (je peux pas utiliser les RTTI à cause des interfaces : on peut pas gagner à tous les coups )
    La méhode de classe d'introspection s'appuie sur le TRelationManager et parcours chaque classe. CQFD



    ainsi pas besoin de recharger l'objet au retour à la fenêtre précédante, hum je vois mieux l'intéret que tu as porté aux interfaces, mais du coup, tu dois ballayer souvent la liste des instances créés, tu m'étonnes que tu as des lenteurs, car à chaque fois que tu crées une instance, tu vérifie la liste si celle-ci n'existe pas déjà (tu as surement un clé genre TableName+TablePrimaryKey ?)

    Ai-je bien compris ? ou pas ?
    Tout à fait ! En fait mes objets (enfin si un jour j'arrive à finir ce que je veux vraiment obtenir) sont indépendant des composants de la VCL. Donc dès qu'un objet est modifié tous les composants en sont informés et mis à jour. Si tu as 2 fénêtres qui affichent le même enregistrement elle sont toutes les 2 actualisées idem pour les autres composants.

    Mes listes donc mes objets TBOObjectList sont naturellement triés sur les clés primaires. En fait pour implémenter l'algo de tri j'ai utilisé le même principe que celui qu'on trouve actuellement dans les SortedList de Delphi.net à savoir je délègue le tri à des interfaces de type ISorter (qui en gros implémente un QuickSort), IComparer (qui compare 2 objets de même classe) et IFinder (qui recherche un item dans la liste en utilisant une recherche binaire si la liste est triée et l'itération classique si la liste n'est pas triée).
    Je ne compare donc pas 2 items POObject sur leur adresse mémoire, ça c'est la comparaison par défaut, mais sur l'égalité de 1 à N de leurs attributs.
    Note que je peux aussi cloner un TBOObjectList tout en lui passant un nouveau ISorter et IComparer ma nouvelle liste est alors triée différemment.
    La recherche est quasi instantannée puisque j'utilise des listes triées + la dichotomie.
    Evidemment tout ceci ne fonctionne que si ma liste est chargée en mémoire pas si j'ai un TQuery derrière : dans ce cas je m'appuie exclusivement sur le TQuery : le tri est celui du TQuery !

    Lol, tu y bosse depuis 2004,
    Ouais c'est pas faux ! T'OH Nan j'déconne je m'y suis interessé à cette époque et puis plus j'ai du passer à auter chose developpement sur le web php et tout ça puis autre chose et hop maintenant que j'ai dû temps je m'y replonge avec de nouvelles idées de nouvelles machines surpuissante par rapport à 2004

    Mais là j'hésite : passer à Delphi 2009 version .net et s'y former donc abandonner de nouveau le framework ...

    En plus je pense que je risque de devoir me plonger dans Indy et les sockets TCP/IP et les threads : décidément je suis pas prêt de l'achever ce framework

  20. #20
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 736
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 736
    Points : 25 645
    Points
    25 645
    Par défaut
    Effectivement, nous avons eu tous les deux une approche assez différente, moi mes objets sont persistants tous le temps, je n'ai jamais pensé à une utilisation non connecté, je n'y voyais pas l'intérêt ... pour l'abstraction DB, effectivement, j'y ai pensé aussi, mais c'est très mal fait, c'est un des trucs que je voudrais remanier ...

    Au final, j'ignore si c'est un FrameWork perso, ou pour le Boulot, mais tu as du y consacrer énormément de temps, et ton approche semble être proche de InstantObjects ... tu as écrit tes objets pour le fun, car cela existait finalement déjà ! Moi, j'ai ré-inventé une couche déjà existante qui devait impérativement géré la valeur nulle (je n'ai pas compris comme l'avoir en InstantObjects, je ne sais même pas si c'est possible, ce qui me semble abhérant !!!)

    Moi comme c'était pour le boulot, je suis parfois allez très vite sur certaines parties, privilégiant la rapidité de programmation pour respecter les délais, et laissant de côter la "rigueur" ou la "beauté" du code ...

    Sinon
    j'ai quand même réfléchi un peu à la question
    Ben, moi, c'était un peu ma méthode, je bricolais un truc, je recommençais jusqu'à ce que ça me plaise ... toujours mon côté artiste impulsif ...


    Pour ta gestion de l'unicité, hum, effectivement, je pense que tu devrais tester les temps SANS la recherche pour voir, car si tu charge 10000 objets, eh bien, tu lance 50 000 000 fois la comparaison !!! (Sum(10000) = 10000 * 10001 / 2), ... je trouve ça pour ma part beaucoup trop lourd, celà implique beaucoup de notification, de comportement implicité du FrameWork, ...

    Pour les clés, moi aussi, je voudrais pouvoir rendre cela plus clean (configurable via le XML) au lieu d'avoir un AutoInc en dur, cela entre aussi dans le Refactoring du DB Provider ...

Discussions similaires

  1. Remplacer caractère ' ( quote ) par "\n"
    Par Eric45 dans le forum C++
    Réponses: 3
    Dernier message: 28/11/2007, 01h56
  2. Réponses: 5
    Dernier message: 30/05/2005, 17h58

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