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

C++ Discussion :

Question de conception


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éprouvé
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 766
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 766
    Par défaut Question de conception
    Bonjour,

    Je dois faire un "désérialisateur" (?) de fichiers CSV.
    Ces fichiers contiennent des entrées correspondant à des champs de structures C++.

    Il y a plusieurs types de fichiers, correspondant à plusieurs types de structures. Il y a une relation d'apparentement entre ces structures, c'est-à-dire qu'il y a des champs communs.

    Prenons les classes suivantes :

    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
    struct A
    {
        int a;
    };
     
    struct B : A
    {
        int b;
    };
     
    struct C : A
    {
        int c1;
        int c12;
    };
    Il me faut désérialiser B et C. Leurs fichiers CSV respectifs seraient, pour une seule entrée :

    et
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    a_Value;c1_Value;c2_Value
    J'avais donc pensé, pour faire ça, à faire une hiérarchie de classe parallèle qui se chargerait de faire correspondre à chaque structure une classe à même d'interpréter le CSV :

    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
    struct IReader
    {
        void read() = 0;
        void readLine() = 0;
    };
     
    // Lit les membres spécifiques à la classe A
    struct AReader : IReader
    {
        void read();
        void readLine();
    };
     
    // Lit les membres spécifiques à la classe A
    struct BReader : IReader
    {
        void read();
        void readLine();
    };
     
    // Lit les membres spécifiques à la classe A
    struct CReader : IReader
    {
        void read();
        void readLine();
    };
    Les fonctions de BReader et CReader feraient appel aux fonctions éponymes de A. À priori, le polymorphisme statique ferait l'affaire, BReader et CReader seraient remplacés par Reader<T>.

    Je ne sais pas si c'est la meilleure méthode. Je suppose qu'il existe un pattern pour ce problème qui doit être assez classique.

    J'attend vos critiques et propositions.

    Merci !

  2. #2
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    Salut

    Comme nous représentons ici des données et pas des comportements, je pense que je prendrais ici une approche par agrégation et pas par héritage.

    Un exemple simple, c'est pas parfait, c'est juste pour exposer le principe:

    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
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    #include <sstream>
    #include <string>
    #include <string>
    #include <cstdlib>
    #include <iostream>
     
    class SemanticA {};  // L'interface métier
     
    struct Unit {
      virtual void fill(std::istream& data_input) = 0;
     protected:
      std::string next(std::istream& input) const {
        std::string result;
        std::getline(input,result,';');
        return result;
      }
    };
     
    struct A : public Unit {
      int a_;
      A() : a_(0) {}
      virtual void fill(std::istream& data_input) { a_ = ::atoi(next(data_input).c_str()); }
     
    };
     
    struct B : public Unit {
      int b_;
      B() : b_(0) {}
      virtual void fill(std::istream& data_input) { b_ = ::atoi(next(data_input).c_str()); }
    };
     
    struct C : public Unit {
      int c1_;
      int c2_;
      C() : c1_(0), c2_(0) {}
      virtual void fill(std::istream& data_input) {
        c1_ = ::atoi(next(data_input).c_str());
        c2_ = ::atoi(next(data_input).c_str());
      }
    };
     
    struct Record {
      A* sa_;
      B* sb_;
      C* sc_;
     
      Record(A* ia=0, B* ib=0, C* ic=0) : sa_(ia), sb_(ib), sc_(ic) {}
     
      virtual ~Record() {
        delete sa_;
        delete sb_;
        delete sc_;
      }
     
      void fill(std::istream& input) {
        if(sa_) sa_->fill(input);
        if(sb_) sb_->fill(input);
        if(sc_) sc_->fill(input);
      }
     
      void display() const {
        if(sa_) std::cout << "A::a_  == " << sa_->a_ << "\n";
        if(sb_) std::cout << "B::b_  == " << sb_->b_ << "\n";
        if(sc_) {
          std::cout << "C::c1_ == " << sc_->c1_ << "\n";
          std::cout << "C::c2_ == " << sc_->c2_ << "\n";
        }
        std::cout << std::endl;
      }
     
      // A toi de voir de quelle interface publique tu as besoin
      // Pour moi, record doit te fournir des méthodes de constructions d'objets
      // polymorphes en fonction des datas qu'il a récupérées
     
      SemanticA* BuildApplicationObject();  // A implémenter
    };
     
    int main() {
      std::istringstream data_a("1;");
      std::istringstream data_ab("1;2;");
      std::istringstream data_ac("1;3;4;");
      std::istringstream data_abc("1;2;3;4;");
     
      Record ra(new A);
      Record rab(new A, new B);
      Record rac(new A, 0, new C);
      Record rabc(new A, new B, new C);
     
      ra.fill(data_a);
      rab.fill(data_ab);
      rac.fill(data_ac);
      rabc.fill(data_abc);
     
      ra.display();
      rab.display();
      rac.display();
      rabc.display();
     
      return 0;
    }
    Affiche:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    A::a_  == 1
     
    A::a_  == 1
    B::b_  == 2
     
    A::a_  == 1
    C::c1_ == 3
    C::c2_ == 4
     
    A::a_  == 1
    B::b_  == 2
    C::c1_ == 3
    C::c2_ == 4
    Pourquoi cette approche ? Cela te permet de représenter plus souplement tes données. Ici je te montre que tu peux charger et lire un type "ABC" sans avoir à créer une nouvelle classe qui héritera de B et de C. De fait, tu n'as besoin de rajouter qu'une classe par "groupe de champs", alors qu'avec l'héritage, tu dois posséder une classe par combinaison de groupe de champs. Je ne connais pas ton cas réel donc je ne peux pas mesurer le gain, mais je pense que c'est intéressant et que ça facilite grandement la maintenance.

    Tu peux même pousser le vice jusqu'à avoir dans Record une collection de Unit qui peut désérialiser tout seul grâce à la première ligne du fichier qui peut contenir les noms de champs.

    Il ne faut pas que tu mélanges la lecture des données et la manière dont tu vas les représenter pendant ton traitement. Tu dois pouvoir changer l'un ou l'autre sans impacter le reste.

    Qui du polymorphisme ?

    Tu vas probablement arguer que tu perd ainsi ton polymorphisme sur chaque structure. Ce n'est pas faux mais n'oublie qu'on parle d'objets qui désérialisent tes données. Si tu as besoin d'objets polymorphes basés sur les données récupérées, alors tu peux charger la classe Record de construire ces objets, et utiliser un polymorphisme propre débarrassé du code de désérialisation.

  3. #3
    Membre éprouvé
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 766
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 766
    Par défaut
    Ton approche est effectivement intéressante, mais sa souplesse ne me sera guère d'utilité.

    Par ailleurs, d'un point de vue sémantique B et C sont effectivement des spécialisations de A, et pas seulement un panachage de données.

    Merci néanmoins de m'avoir pondu tout ce code !

  4. #4
    Membre éclairé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Par défaut
    Un fichier CSV n'est-ce point un fichier texte ?
    Si ça peut t'aider, j'ai un code simple(iste?) pour (dé)sérialiser des lignes de texte. C'est du code générique directement inspiré de boost. AMA il doit être facile à changer pour s'adapter aux marqueurs CSV.

    Et, perso, un cochon c'est un cochon et une vache c'est une vache, même s'ils ont 4 pattes tous les deux
    Je veux dire que je représente les données externes par des structures simples, si pas POD au moins non intrusives ni polymorphiques.

  5. #5
    Membre Expert
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Par défaut
    D’abord, une première remarque : un fichier csv ne se lit pas forcément ligne par ligne (sachant qu’il n’y a pas de norme CSV, mais seulement des pratiques courantes). Typiquement, tu peux, dans certains cas, avoir des retours chariot dans tes valeurs.

    Donc les approches qui visent à lire le fichier ligne par ligne, pour ensuite parser les lignes et les séparer en champs, sont à mon avis à proscrire. Une bonne vieille analyse caractère par caractère, avec un parseur à 4 ou 5 états (plus sûr du nombre exact nécessaire) est nécessaire, et s’implémente très facilement..

    Ensuite, personnellement, quand j’ai à traiter des CSV, j’ai tendance à préférer une approche évènementielle « à la sax », plutôt qu’une approche ou le consommateur demande les enregistrements.

    Donc au final, tu as le parseur, qui va appeler une callback à chaque champ ainsi qu’à chaque fin d’enregistrement (ou début, ou les deux, au choix). Ça permet, du point de vue du consommateur, de s’abstraire des détails d’implémentation du csv (comme le séparateur utilisé, le marqueur de fin de ligne, les éventuels guillemets au niveau des champs, …), qui relèvent du parseur.

    Ensuite, la construction de tes objets se fera au niveau de la callback appelée par le parseur. Ici, si tes objets ne sont pas « mélangés » dans le csv, il n’y a pas spécialement de difficulté. À chaque nouvel enregistrement, tu crées un nouvel objet, et à chaque colonne lue, tu remplis la valeur correspondante.

    L’avantage étant que c’est traversant : si tu n’utilises pas certaines colonnes, tu ne les stockes pas dans un temporaire, idem pour les objets dont tu ne te servirais pas.

Discussions similaires

  1. [WIN32] question de conception
    Par gdpasmini dans le forum MFC
    Réponses: 4
    Dernier message: 10/07/2006, 11h08
  2. [Data-sources] Questions de conception
    Par xfacq dans le forum Général Java
    Réponses: 1
    Dernier message: 02/06/2006, 01h32
  3. [VB.net] Question sur conception
    Par arno2000 dans le forum Windows Forms
    Réponses: 1
    Dernier message: 14/04/2006, 08h35
  4. [XML]Question de conception
    Par nana1 dans le forum Persistance des données
    Réponses: 17
    Dernier message: 17/11/2005, 09h34
  5. [Strategie][GUI]Petite question de conception
    Par bischof dans le forum Interfaces Graphiques en Java
    Réponses: 3
    Dernier message: 26/10/2004, 22h31

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