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 :

sémantique et rapport de classe


Sujet :

C++

  1. #1
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 294
    Billets dans le blog
    2
    Par défaut sémantique et rapport de classe
    Bonjour,

    j'ai une question très peu précise qui me taraude depuis quelques jours, et j'aurais aimé avoir d'autres avis que le mien.

    Nous avons une classe Data, qui contient quelques données. Pas beaucoup: disons 4 variables membres.
    Cette classe a une sémantique de valeur; elle va être stockée dans des conteneurs, triée, copiée, etc.
    Elle sera donc dotée du big 5 (dtor, default ctor, copy ctor, move ctor et operator =).

    Maintenant voici mon problème. Cette classe Data va être instanciée et construite de diverses façons. Elle peut être construite à partir de trames reçue par le module réseau, à partir de données récupérées par le gestionnaire de configuration, via l'interface d'accès aux bases de données, etc.
    Mon interrogation consiste à trouver la meilleure façon de gérer ces multiples façons de créer mon objet. La première tentation consiste à passer par une factory. Mais étant donné que mon objet est petit et qu'il n'évoluera pas, j'ai l'impression que de passer par une factory c'est un peu le "bulldozer pour planter une fleur", si vous voyez ce que je veux dire.
    Alors je réfléchissais à ajouter des fonctions membres à ma classe Data, du style:
    void CreateFromRecord( const DataBaseRecord & record );
    void CreateFromFile( ifstream & file );
    // etc.

    Cette solution me plait plus, car ça simplifie mon design, ça fait une classe en moins etc, bref, ça simplifie la maintenance. Mais je ne sais pas, j'ai le sentiment qu'il y a quelque chose qui ne va pas avec cette façon de faire. C'est pourquoi je voulais avoir votre avis, vos réflexions.

  2. #2
    Membre chevronné
    Inscrit en
    Décembre 2010
    Messages
    290
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 290
    Par défaut
    Je suppose que tu y as pensé, mais en faisant ça tu rends ta classe Data dépendante d'un tas d'autres trucs comme DataBaseRecord, ce que tu n'as pas forcément envie de faire.
    De plus, ajouter des fonctions membres rend l'objet Data plus complexe à maintenir. Est-ce que CreateFromRecord() en tant que fonction membre peut garantir des invariants sur Data qu'une fonction non-membre ne pourrait pas garantir ?

    Ensuite, je ne connais pas ton cas, mais il y a peut-être une façon "presque naturelle" de créer un objet Data à partir d'une séquence d'éléments. Si c'est le cas, alors pourquoi ne pas proposer constructeur template dans le style de :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    template<typename It> Data::Data (It begin, It end) // Construit l'objet à partir d'une séquence
    Et avoir des fonctions du style CreateFromRecord () qui appellent ce constructeur à partir d'une séquence qu'elles ont construites ?

  3. #3
    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,

    As-tu pensé au fait que, quelle que soit l'origine des valeurs que tu voudras donner aux membres de ta classe, ce que tu veux réellement faire n'est jamais que de la "dé-sérialisation"

    Et, le fait est que la récupération de ces valeurs est, de toutes manières, dépendante de leur origine.

    Comme je présume que tu as bien fait les choses et que tu as d'office un constructeur qui prend comme attribut les valeurs des quatre membre, pourquoi vouloir se mettre martel en tête Chaque système de "dé-sérialisation" peut avoir une fonction spécifique qui crée la donnée selon ses propres spécificités et "basta"
    [EDIT]
    Au pire, rien ne t'empêche de créer une interface template (utilisée par CRTP) proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    template <typename CRTP>
    class IDataCreator{
        Data createData() ;
    protected:
        IDataCreator(CRTP & real):real_(real){}
        ~IDataCreator(){}
    private
    CRTP & real_;
    };
    class DBaseReader : public IDataCreator<DBaseReader>{
        friend class IDataCreator<DBaseReader>;
        /* ... */
    };
    et de fournir une spécialisation totale de create data pour chaque contexte spécifique, mais le résultat sera identique (si ce n'est que le code indiquera explicitement que les classes dérivées sont effectivement susceptibles de créer la donnée )
    [/EDIT]
    @phil1981 Ton code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    template<typename It> Data::Data (It begin, It end) // Construit l'objet à partir d'une séquence
    présente un inconvénient majeur : celui d'impliquer que les quatre membres soient de type identique, à moins de passer par un boost::variant ou similaire.

    Ce n'est pas forcément garanti (en tout cas, r0d n'a rien dit à ce sujet) et la solution d'un variant (qu'il soit de boost ou de Qt ou de n'importe quelle autre origine) est peut etre aussi un peu overkill pour une donnée du genre
    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

  4. #4
    Membre chevronné
    Inscrit en
    Décembre 2010
    Messages
    290
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 290
    Par défaut
    Citation Envoyé par koala01 Voir le message
    @phil1981 Ton code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    template<typename It> Data::Data (It begin, It end) // Construit l'objet à partir d'une séquence
    présente un inconvénient majeur : celui d'impliquer que les quatre membres soient de type identique, à moins de passer par un boost::variant ou similaire.
    Je crois pas me tromper en disant "pas forcément". Il faut que les quatre membres puissent être convertis depuis une séquence d'éléments du même type. Un exemple : si mes 4 membres sont respectivements de type "float", "int", "double" et pourquoi pas "Date" (un type utilisateur), alors pourquoi ne pas passer en argument un vecteur de 4 "std::string", si tu as des moyens de convertir depuis une chaîne de caractères ?
    Je t'accorde que c'est un peu "overkill", toutefois.
    Mon idée c'était surtout de démontrer que le constructeur devrait être indépendant de la source des données. Comme tu l'exprimes, un constructeur pourrait prendre les 4 membres en paramètre (ou en déduire certains d'entre eux, si possible), et laisser à une fonction de déserialisation le soin d'appeler ce constructeur.

  5. #5
    Membre très actif
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Janvier 2010
    Messages
    434
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Janvier 2010
    Messages : 434
    Par défaut
    Bonjour,

    Si tu a des types de paramètres différents en fonction de l'entrée tu peux toujours utiliser la surcharge.
    Sinon le plus propre est de passer par un factory methode et dans l'idéal avec une interface comme ça tu à du couplage faible.

    Ensuite si tu a un objet différent en fonction de ton entrée tu peux toujours faire une surcharge de l'opérateur =.

    Il manque pas mal d'info pour voir réelement qu'elle est la meilleur manière de faire, etant donné que la meilleur solution dépend du contexte dans sa globalité

  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
    Citation Envoyé par phi1981 Voir le message
    Je crois pas me tromper en disant "pas forcément". Il faut que les quatre membres puissent être convertis depuis une séquence d'éléments du même type. Un exemple : si mes 4 membres sont respectivements de type "float", "int", "double" et pourquoi pas "Date" (un type utilisateur), alors pourquoi ne pas passer en argument un vecteur de 4 "std::string", si tu as des moyens de convertir depuis une chaîne de caractères ?
    Je t'accorde que c'est un peu "overkill", toutefois.
    Mon idée c'était surtout de démontrer que le constructeur devrait être indépendant de la source des données. Comme tu l'exprimes, un constructeur pourrait prendre les 4 membres en paramètre (ou en déduire certains d'entre eux, si possible), et laisser à une fonction de déserialisation le soin d'appeler ce constructeur.
    A priori, le comportement que tu décris là n'est surtout pas le rôle d'un constructeur

    Maintenant, je t'accorde que tu pourrais avoir une fonction libre pourrait faire l'affaire. Elle serait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    template <typename iterator>
    Data create(iterator begin, iterator end){ // reçoit d'office des itérateurs vers des std::string
        std::stringstream ss;
        while(begin != end){
            ss<<*begin;
            ++begin;
        }
       int first;
       float second;
       double third;
       std::string fourth;
       ss>>first>>second>>third>>fourth;
       return Data(first,second,third,fourh);
    }
    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 chevronné
    Inscrit en
    Décembre 2010
    Messages
    290
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 290
    Par défaut
    Citation Envoyé par koala01 Voir le message
    A priori, le comportement que tu décris là n'est surtout pas le rôle d'un constructeur
    Pas faux, puisque effectivement, une fonction libre pourrait faire l'affaire. Merci d'avoir clarifié.

  8. #8
    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
    Si tu as besoin que ces fonctions soient membres, c’est qu’elles ont besoin d’accéder aux membres privés, non ?

    Sinon, la question ne se pose même pas : une fonction libre est parfaitement appropriée.

    Avantage :*ta classe n’est pas directement dépendante des autres, et la glu qui fait le lien entre deux modules se retrouve isolée, ni dans un module ni dans l’autre (le meilleur choix à mon avis, même si si tu n’as qu’une classe concernée ça peut sembler surdimensionné).

    Si tu as besoin qu’elles aient accès aux membres privés, tu peux t’en sortir avec une friend template. Ça expose ta classe au monde entier, mais en même temps, ce n’est pas non plus la mort vu qu’elle a sémantique de valeur.

  9. #9
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 294
    Billets dans le blog
    2
    Par défaut
    Bonjour et merci à tous pour vos propositions.
    Notez que je n'ai pas donné de détails, c'était à dessein, car je souhaitais orienter la discussion vers des généralités.
    Après, concrètement, comme je l'ai dit, j'aimerais ne pas créer un type (classe ou fonction) supplémentaire, donc pas de factory.
    Effectivement, mon problème est en fait un problème de sérialisation/dé-sérialisation. J'aimerais que tout le code relatif à la dé-sérialization soit implémenté dans la classe elle-même, car d'expérience, je pense qu'il est bel et bon qu'une classe puisse débrouiller toute seule pour se construire (c'est la raison d'être d'un constructeur qui prend tous les paramètres nécessaires en arguments). Un tel choix implique des dépendances de mon objet vers certaines interfaces (le module réseau par exemple), mais que la dépendance soit dans ce sens ou dans l'autre, ça revient au même n'est-ce pas?

  10. #10
    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
    Citation Envoyé par r0d Voir le message
    Bonjour et merci à tous pour vos propositions.
    Notez que je n'ai pas donné de détails, c'était à dessein, car je souhaitais orienter la discussion vers des généralités.
    Après, concrètement, comme je l'ai dit, j'aimerais ne pas créer un type (classe ou fonction) supplémentaire, donc pas de factory.
    Effectivement, mon problème est en fait un problème de sérialisation/dé-sérialisation. J'aimerais que tout le code relatif à la dé-sérialization soit implémenté dans la classe elle-même, car d'expérience, je pense qu'il est bel et bon qu'une classe puisse débrouiller toute seule pour se construire (c'est la raison d'être d'un constructeur qui prend tous les paramètres nécessaires en arguments).
    Cela peut se comprendre, mais, il faut te dire qu'à chaque fonction membre ou assimilée (une fonction libre amie ou les fonctions membres de classes amies qui manipulent directement tes données), tu perds en encapsulation "générale": c'est à chaque fois une fonction que tu risques de devoir modifier si tu décide de modifier la représentation interne de tes données.

    Ne me fais pas dire ce que je n'ai pas dit : l'amitié améliore l'encapsulation en évitant d'avoir à exposer plus d'éléments, mais, l'un dans l'autre, une classe qui n'expose qu'une fonction qui utilise la représentation interne des données de ta classe est "mieux encapsulée" qu'une fonction qui en expose 2, que ce soit des fonctions membres ou des fonctions qui profitent de l'amitié

    Un tel choix implique des dépendances de mon objet vers certaines interfaces (le module réseau par exemple), mais que la dépendance soit dans ce sens ou dans l'autre, ça revient au même n'est-ce pas?
    Ah, non, pas du tout!

    S'il est (et encore, il faut y mettre un sérieux bémol ) "compréhensible" que le réseau dépende des données métiers, il n'y a absolument aucune raison pour que les données métier dépendent du réseau!

    Car, si tu "modularise" tout cela et que tu as un module "business", et un autre module "network", network devra disposer de business pour être en mesure... d'avoir des données à émettre / recevoir. Il y a du sens à le faire de la sorte.

    Mais, par contre, si c'est business qui dépend de network, cela implique que, si tu n'a pas network -- soit parce qu'il bugge, soit parce que tu ne l'a pas encore développé, soit parce que la dll n'a pas été fournie, ou que sais-je -- tu es dans l'incapacité d'utiliser business! Tu avoueras que ce serait un peu "fort de café" alors qu'il existe quantité d'autres solutions pour créer tes données business (ou pour les récupérer, si tu préfères).

    S'il y a bien un module qui ne devrait dépendre d'aucun autre, c'est bien le module business, car, à l'extrême limite, il doit être en mesure de se contenter de "n'importe quelle entrée" pour obtenir les données qu'il doit représenter. D'une certaine manière, c'est le module "central" dans le sens où toutes les dépendances -- directes ou indirectes -- aboutissent tôt ou tard sur lui. Tous les autres modules dépendront sans doute "fatalement" de business de manière directe ou indirecte, et certains modules peuvent même dépendre de plusieurs autres modules (en plus de business, s'entend ), bien que ce ne soit pas très sain comme situation. mais, si tu peux te contenter de la SL sans meme en arriver à utiliser un boost::any ou un boost::variant (ou similaire) pour ton business, c'est la meilleure chose qui puisse t'arriver
    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

  11. #11
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 294
    Billets dans le blog
    2
    Par défaut
    C'est très jute tout ça.
    Bon, je vais encore y réfléchir...

  12. #12
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 296
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 296
    Par défaut
    Citation Envoyé par r0d Voir le message
    je pense qu'il est bel et bon qu'une classe puisse débrouiller toute seule pour se construire (c'est la raison d'être d'un constructeur qui prend tous les paramètres nécessaires en arguments). Un tel choix implique des dépendances de mon objet vers certaines interfaces (le module réseau par exemple), mais que la dépendance soit dans ce sens ou dans l'autre, ça revient au même n'est-ce pas?
    Une classe doit pouvoir être construite dans un état pertinent. C'est capital. Ce devrait être l'invariant de tout objet construit.

    Mais il ne faut pas coupler la classe à d'autres concepts sans rapport (std::cin, sockets, fichier xml, fichier json, IHM Qt, IHM MFC, ...). Au mieux on arrive à inverser la dépendance et à déduire une interface de construction et la classe ira piocher dedans. Mais ... c'est se compliquer la vie pour rien. Avoir un constructeur qui prend tous les paramètres qui vont bien est largement suffisant.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  13. #13
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 294
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    Avoir un constructeur qui prend tous les paramètres qui vont bien est largement suffisant.
    Effectivement, je pense que c'est la meilleure solution.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 20
    Dernier message: 09/04/2020, 17h02
  2. Réponses: 5
    Dernier message: 01/04/2008, 21h58
  3. Réponses: 3
    Dernier message: 22/06/2007, 22h14
  4. Changer le .class (dont le rapport se sert) à chaud
    Par tiboudchou dans le forum BIRT
    Réponses: 1
    Dernier message: 11/07/2006, 09h23
  5. Réponses: 18
    Dernier message: 08/04/2006, 10h39

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