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 :

Classe Saver d'une classe X - Friend et encapsulation


Sujet :

C++

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    11
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 11
    Par défaut Classe Saver d'une classe X - Friend et encapsulation
    Voila, je voulais avoir votre avis sur un petit problème de design.

    En effet, je dispose d'une classe X qui matérialise des entités dans un espace physique.
    Ces entités doivent pouvoir être chargées et sauvegardées depuis différentes sources : un format de fichier, une base de donnée, etc...
    Mais, avec tous les articles théoriques que je digère en ce moment, je me pose beaucoup de questions sur la bonne solution à adopter

    Pour préserver l'encapsulation, j'ai décidé de ne pas exposer les données internes de X, juste des méthodes (translate(), rotate()). En revanche, il parait évident qu'une classe en particulier doit pouvoir accéder à ces données internes : la classe qui se charge des sauvegardes.

    J'en arrive donc à un dilemme : le sain principe de l'encapsulation me défend d'exposer les entrailles de X, et pourtant on peut estimer que la classe Saver est "au dessus de ça".
    Une solution serait de confier à X le soin de se sauvegarder elle même, mais ça me semble "bloater" les attributions de X, qui ne devrait pas avoir à se soucier de la manière dont elle est gérée (donc sauvegardée).

    La solution qui vient tout de suite à l'esprit, c'est de déclarer Saver::save(X* x) friend de ma classe X.

    Sauf que la classe Saver est une interface, et que le friendship ne s'hérite pas, et que je ne sais pas à l'avance quels classes dérivées l'utilisateur va implémenter. Donc friend ne résout pas mon problème, en tout cas pas comme ça.

    Une solution pas trés élégante serait de déclarer Saver friend de ma classe X, et de créer un getter protected pour chaque attribut de X, que les classes dérivées de Saver appeleraient.

    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
     
    class Saver;
     
    class X
    {
    friend Saver;
    //...
    };
     
    class Saver
    {
    //...
    protected:
    std::string getIdentifier(X* x) { return x->mIdentifier; }
    Vector3 getPosition(X* x) { return x->mPosition; }
    Vector3 getVelocity(X* x) { return x->mVelocity; }
    };
    Mais du coup ça me demande de mettre à jour mon interface Saver ET ses implémentations à chaque modification de la classe X.

    Je recquiert donc vos avis éclairés sur cette question : y-a-t il d'autres manières de voir le problème, d'autre solutions ?

  2. #2
    Membre Expert
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Par défaut
    Puisque tu parles de sauver/charger des structures de données alors pourquoi ne pas les sérialisé?
    boost::serialisation te permet de faire ça de façon intrusive ou non.

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    11
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 11
    Par défaut
    C'est une idée (je connaissais pas) pour sauvegarder vers des fichiers, mais de toute façon il me faudra un Saver vers une base de données sérializée, donc je dois quand même résoudre ce dilemme.

  4. #4
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    Pourquoi ne pas, tout simplement, déclarer l'opérateur << vers un ostream et l'opératur >> vers un istream friend de ta classe, voire, créer une interface séparée Serializable<T> avec une unique fonction virtuelle pure serialize() et une autre DeSerializable<T> avec une unique fonction virtuelle pure deSerialize()

    Dans le premier cas (opérateurs << et >>), tu implémente les fonctions non pas dans le cpp correspondant à ta classe, mais dans celui correspondant au saver, dans le second, tu place les membre de ta classe X en accessibilité protected (au lieu de private) et tu utilise l'héritage multiple sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class SerializableX : public X, public Serializable<LeSaverUtilise>
    {
        public:
            SerializableX(X const &);
            virtual void serialize() const;
    };
    class DeSerializableX : public X, public DeSerializableX<LeReaderUtilise>
    {
        public:
            DeSerializableX(LeReaderUtilise &);
            virtual void deSerialize();
    };
    (classe qui peuvent n'être déclarées et définies que dans le fichier d'implémentation (cpp) du saver/reader, et qui seront réelleement manipulées par eux).

    Tu subira peut être une explosion des classes (deux classes rajoutées par classes à sérialiser / désérialiser et par type de reader ou de saver), mais la portée de ces classes supplémentaires est suffisamment limitée pour que cela ne présente que le problème de ne pas oublier de les créer
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Membre éclairé
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    301
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 301
    Par défaut
    Juste pour être sûr d'avoir bien compris ta solution koala01, pour déserialiser, c'est bien comme cela qu'il faudrait procéder?:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    X elt;
    reinterpret_cast<DeSerializableX&>(elt).deSerialize();
    Avec comme restriction de ne pas ajouter d'attribut membre dans DeSerializableX?
    Et si X n'a pas de constructeur par défaut, un truc du genre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    X* elt;
    X = reinterpret_cast<X*>(new DeSerializableX(unReader));

  6. #6
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    bien plus simple, encore:
    pour sérialiser ton objet, ce serait
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    X * temp;
    /* récupération de l'objet à sérialiser */
    SerialisableX obj(*temp);
    obj.serialize();
    Pour désérialiser un objet (la fonction aurait en fait le prototype de X deserialize() ou de X* deserialize() selon le cas et les besoins)
    DeserializableX temp;
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    X obj=temp.deserialize();
    /* ou */
    X* ptr = temp.deserialize();
    Il n'est aucun besoin de faire des cast à la barbare
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  7. #7
    Membre éclairé
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    301
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 301
    Par défaut
    Ok merci , c'est le prototype
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    virtual void deSerializable();
    qui m'avait fait pencher pour les casts ... qui ne sont plus nécessaires avec X ou X* en valeur de retour

Discussions similaires

  1. éxecuter une classe qui contient une classe annonyme
    Par star-watcher dans le forum Débuter avec Java
    Réponses: 9
    Dernier message: 09/03/2009, 01h26
  2. Instanciation d'une classe fille depuis une classe mère
    Par khaled-benloucif dans le forum Langage
    Réponses: 2
    Dernier message: 30/01/2009, 23h59
  3. Réponses: 7
    Dernier message: 25/08/2008, 16h13
  4. Réponses: 2
    Dernier message: 02/02/2008, 07h37
  5. Réponses: 8
    Dernier message: 20/07/2007, 14h28

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