Partage
  • Partager sur Facebook
  • Partager sur Twitter

Structure avec attributs publics

Sujet résolu
Anonyme
    10 décembre 2019 à 18:57:08

    Bonsoir,

    Après avoir lu ceci, j'apprends qu'en C++, une classe et une structure sont similaires

    Une structure avec des attributs publics est-elle une atteinte au principe d'encapsulation?

    Un exemple :

    struct A
    {
        //attribut publics
        int x, y;
    
    };

    Merci.

    • Partager sur Facebook
    • Partager sur Twitter
      10 décembre 2019 à 19:20:01

      Si ta classe/structure représente une entité, c'est une mauvaise idée oui.

      Mais tu peux très bien rajouter des champs private dans ta structure, c'est juste la visibilité par défaut qui change entre struct et class.

      • Partager sur Facebook
      • Partager sur Twitter
      Anonyme
        10 décembre 2019 à 19:37:39

        Mais l'on ne perd pas un peu l'intérêt d'une structure héritée du C? Ma structure n'est que le type d'un attribut d'une autre classe :

        struct Anomaly
        {
            //...
        
            AnomalyID id;
            std::string name;
            unsigned short int quality;
        };
        
        class Planet
        {
            public:
                Planet();
                Planet(const Planet& other);
                //entre autres...
        
            private:
        
                std::vector<Anomaly> anomalies;
                //entre autres...
        };
        

        -
        Edité par Anonyme 10 décembre 2019 à 19:44:18

        • Partager sur Facebook
        • Partager sur Twitter
          10 décembre 2019 à 19:55:23

          Tu peux faire la même chose avec une classe, mais ça n'a pas vraiment d'intérêt si tu mets tout tes champs publiques.

          Utilises ce dont tu as besoin, si tu as besoin d'avoir une visibilité public par défaut, tu utilises une structure (POD, traits) sinon une classe.

          • Partager sur Facebook
          • Partager sur Twitter
            10 décembre 2019 à 20:47:08

            PArler d'encapsulation sans envisager les invariants n'a que peu de sens car l'encapsulation est là pour aider à garantir les invariants d'un objet.

            Donc la question: ta structure a-t-elle un invariant? Si non est la réponse, alors il n'y aura guère de mal à laisser les attributs publics, d'autant si la classe n'offre aucun service car ce n'est alors qu'un simple agrégat de données.

            • Partager sur Facebook
            • Partager sur Twitter
            C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
              11 décembre 2019 à 3:59:38

              Salut,

              Scover a écrit:

              Une structure avec des attributs publics est-elle une atteinte au principe d'encapsulation?

              Le principal problème, c'est que le principe même de l'encapsulation est trop souvent mal compris, parfois même par certains professionnels :'(.

              Alors mettons les choses au point tout de suite: si, quand tu parle du principe d'encapsulation, tu crois le respecter en plaçant tes données dans l'accessibilité privée et en exposant des accesseurs (Type getXXX() ) et des mutateur (void setXXX(Type) ), TU AS TOUT FAUX!!!.

              Le principe d'encapsulation, cela consiste à respecter une loi appelée Loi de Déméter, qui dit, en substance que

              Si un objet de type A (mettons, pour l'exemple, une Voiture) utilise en inter un objet de type B (mettons, pour être cohérent avec la voiture, un réservoir à carburant) l'utilisateur d'un objet a de type A (par exemple, ta voiture qui rentre au garage) n'a aucun besoin de connaître le type B (autrement dit, le réservoir) pour pouvoir manipuler son objet de type A (autrement dit, la voiture).

              Et, quand tu y réfléchis bien, c'est exactement comme cela qu'une voiture est faite:

              Elle va t'exposer -- au travers de son interface (la trappe dans laquelle tu fais couler le carburant, la jauge qui t'indique son état de remplissage, le bouton lumineux qui te dit quand tu es sous réserve, l'ordinateur de bord qui te fournit des informations telles que ta consommation moyenne et actuelle, le nombre de km que tu peux parcourir avant de tomber en panne, etc) -- toute une série d'informations relatives à la présence d'un réservoir.

              Mais tu n'auras -- a priori -- jamais besoin de passer en dessous de la voiture pour aller "bidouiller" directement sur le réservoir ;) (même s'il m'est arrivé par deux fois de devoir le faire dans des situations très particulières :'().

              Alors, comprenons nous bien: l'idée qui se cache derrière la notion d'encapsulation est de n'exposer à l'utilisateur d'une classe (ou d'une strucutre) que des services.  C'est à dire: un ensemble de fonctions qui permettront à l'utilisateur en question soit de poser des questions sur l'état de l'objet qu'il manipule, soit de "donner un ordre" à cet objet.

              A ce titre, certains accesseurs peuvent représenter des services que l'on est en droit d'attendre de la part de certaines classes.  Il va de soi que, si tu envisage de créer une classe Personne, l'un des services que tu vas attendre de sa part, c'est sans doute qu'elle puisse répondre à la question "quel est ton nom?", et qu'une fonction proche de std::string const & /*get_*/name() const est, du coup, parfaitement cohérente.

              Mais, si je reprend l'exemple de ma classe Voiture et de son réservoir, il n'y a aucune raison d'envisager d'exposer une proche de Reservoir const & /*get_*/reservoir() const.

              Il va de soi que, si tu ne trouve déjà pas de raison valable pour exposer un accesseur, tu auras beaucoup de mal à trouver une raison valable pour expoer un mutateur (c'est même -- a priori -- carrément impossible) ;).

              Arrivé à ce point, tu te dis peut-être qu'il reste quand même encore le cas de toutes ces données pour lesquelles on a estimé qu'il était "cohérent" de fournir un accesseur, me trompes-je?

              Eh bien, figure toi qu'un type nommé scott meyers nous a donné le meilleur élément de réponse que l'on puisse trouver, car il a dit, je cite:

              Make your interfaces easy to use correctly and hard to use incorrectly

              (Rendez vos interfaces facile à utiliser correctement et difficiles à utiliser de manière incorrecte)

              Que crois tu qu'il voulait dire?  Et bien, en gros, qu'il n'y a -- a priori -- qu'une seule personne qui connaisse toutes les règles qui doivent être respectées lorsque l'on manipule les données interne d'une classe : C'est la personne qui est occupée à développer la classe en question.

              Et encore: cette connaissance de toutes les règles dont je parle n'a qu'une "durée de vie" particulièrement limitée, parce que, dés qu'il "passera à autre chose", dés qu'il commencera à travailler sur une autre classe, il commencera à les oublier une à une.

              L'idée est donc que, tant que le développeur a bien toutes ces règles en tête, il devrait réfléchir à l'ensemble des services qui sont réellement utiles à l'utilisateur de la classe, de manière à en fournir une implémentation qui ... empêchera l'utilisateur de faire une connerie.

              Arrivé à ce point, tu devrais te rendre compte que, si tu exposes un mutateur, tu donnes à l'utilisateur de ta classe une responsabilité qu'il n'aurait jamais du avoir, à savoir : effectuer tous les calculs et toutes les vérifications requis pour obtenir la "nouvelle valeur".

              Alors, tu pourrais sans doute demander pourquoi je dis que l'utilisateur (qui n'est -- au demeurant -- personne d'autre que toi, une fois que tu as fini d'écrire le code de ta classe, et que tu commences à l'utiliser) ne devrait jamais avoir cette responsabilité?

              Et bien, la réponse est toute simple : C'est parce que la loi de Murphy aidant, tu peux avoir la certiude que, tôt ou tard, "un utilisateur" (que ce soit toi ou quelqu'un d'autre n'a aucune espèce d'importance) finira par oublier une des règles imposée par ta classe et fera "s'écrouler le château de cartes".

              Il ne faut même pas perdre son temps à se demander SI ca arrivera un jour, car c'est garanti sur facture.  On peut tout de suite passer à la question la plus embêtante à savoir QUAND cela arrivera.  Et, pour notre malheur, la réponse sera toujours la même: au pire moment qui soit.

              Maintenant que tu as -- je l'espère -- (n'hésites pas à relire plusieurs fois ce qui précède, ni à poser des question à ce sujet :D ) une compréhension correcte du principe d'encapsulation, revenons au noeud du problème.

              Mais pour ce faire, je vais modifier un peu ta question et lui donner la forme de

              Quand le principe d'encapsulation est il réellement important?

               Tant il est vrai que le fait de placer des données dans l'accessibilité y porte effectivement atteinte.  La bonne question étant de savoir s'il faut s'en préoccuper ou non ;)

              Pour apporter une réponse cohérente à cette question, il faut avoir pris conscience du fait que, quand on manipule des "type définis par l'utilisateur" (j'utilise ce terme pour mettre les classes et les structures dans le même sac, vu qu'il n'y a pas de différence majeure entre les deux :D), on peut -- a priori -- les classer en deux grandes catégories:

              Pour aller un peu plus loin que ces deux entrées, disons que la sémantique de valeur représente n'importe quel concept nous permettant de manipuler un "référentiel quelconque" (des notions comme le poids, la vitesse, la couleur, la date, la position, la durée, j'en passe, et surement de meilleures).

              L'une des caractéristiques majeures de ces types de donnée -- du moins, à mon sens -- et qui n'est pas indiquée dans le lien étant que ces type de données sont d'excellents candidats à une utilisation exclusivement constante.  Mais, laisse moi t'expliquer mon point de vue:

              Mettons que je sois occupé à créer un type de donnée permettant de représenter une couleur, une date ou une position.  Il semble cohérent de se dire que, si je viens à modifier une seule des données qui compose mon type de donnée, j'obtiens ... une donnée tout à fait différente :

              le 11/12/2019 est en effet une date tout à fait différente que le 10/12/2019, que le 11/11/2019 ou que le 11/12/2018.  Et il en vas de même avec tous les autres types qui entrent dans la catégories des "type à sémantique de valeur".

              Alors, bien sur, cette idée de ne les manipuler que sous une forme strictement constante rendrait très certainement les choses bien plus compliquées car la manipulation de ces types en deviendrait fastidieuse et nécessiterait un grand nombre de copies.

              Il n'empêche que, même si cela doit rester un voeux pieu, l'utilisation exclusivement constante reste un ojbectif majeur potentiel,  auquel il serait intéressant de ne renoncer que "contraint et forcé".

              Mais, quitte à devoir y renoncer, bah... Si les données internes sont dans l'accessibilité privée, on aura de toutes manières fourni un accesseur pour chacune d'elle et, l'un dans l'autre, on pourrait arguer que:

              • rien n'empêche de fournir des fonctions libres permettant la modification des données en assurant les règles spécifiques
              • généralement les règles de modifications sont suffisamment simples et connues que pour pouvoir laisser à l'utilisateur la responsabilité de s'assurer qu'elles soient suivies

              pour justifier la présence d'un mutateur sur chacune des données interne.

              Oui, mais, d'un autre coté, si on doit:

              • fournir un constructeur reprenant toutes les données (car la création d'une variable doit fournir une donnée "directement utilisable"
              • fournir un accesseur sur chacune des données interne (car la réponse à la question "quel(le) est ton|ta XXX?" reste un service attendu de la classe)
              • fournir un mutateur sur chacune des données (car il est entendu que l'utilisateur peut vouloir modifier chacune des données séparément)

              uniquement parce qu'on a décidé de mettre ces données dans l'accessibilité privée et alors que cela n'apporte rien du point de vue de l'encapsulation, on est parfaitement en droit de se poser la question "n'est-on pas en train de perdre notre temps inutilement à faire tout cela?".

              Et, juste après, viendra la question qui fache: "ne serait-il pas plus facile de mettre toutes ces données dans l'accessibilité publique, ''et basta''?".

              Chacun trouvera midi à sa porte en répondant à cette question.  J'espère sincèrement que ce sera aussi ton cas (en tout cas, tu as toutes les cartes en main pour faire un choix éclairé ;) )

              -
              Edité par koala01 12 décembre 2019 à 21:20:06

              • Partager sur Facebook
              • Partager sur Twitter
              Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
              Anonyme
                12 décembre 2019 à 19:23:06

                Bon bah je laisse mes structures alors.. mieux ça plutôt qu'un std::pair...

                Merci pour vos réponses

                -
                Edité par Anonyme 12 décembre 2019 à 19:24:21

                • Partager sur Facebook
                • Partager sur Twitter
                  12 décembre 2019 à 22:09:01

                  Scover a écrit:

                  Bon bah je laisse mes structures alors.. mieux ça plutôt qu'un std::pair...

                  Autant cela rassure de voir que je n'ai pas forcément écrit toute cette tirade pour rien, car tu semble en avoir compris le sens, autant je crois que tu te trompes de cible en visant std::pair...

                  Si tu avais parlé d'accesseurs et de mutateurs, j'aurais été tout à fait d'accord, mais, comme tu vises explicitement std::pair (et, par conséquent, son pendant supportant "un nombre inconnu de données" : std::tuple), je me vois sommes toutes obligés de réagir :'(

                  Car il y a, malgré tout, quelques cas bien particuliers dans lesquels le nom associé à une donnée a beaucoup moins d'importance que le type de la donnée et la position de cette donnée dans la structure.

                  Il y a même certains cas, bien que cela m'arrache la langue de le reconnaitre -- étant l'un des plus fervents promoteur d'une politique de nommage stricte -- ou le nom risque "de t'engluer" dans une logique "trop restrictive" parce qu'il est "trop précis" par rapport à l'usage potentiel que tu pourrais faire de la donnée.

                  C'est d'autant plus vrai avec C++17, parce que nous pouvons désormais écrire un code qui ressemblerait à

                  /* une fonction qui extrait un entier et une 
                   * chaine de caractères de "quelque part"
                   */
                  auto extractIntAndString(){
                      /* On se fout pas mal du "où" et du comment ...
                       * Ce qui importe, c'est que l'on finisse avec un 
                       * int (nommé value, dans le cas présent)
                       * et une chaine de caractères (nommée str)
                       */
                      int value{15};
                      std::string str{"Jean"};
                      /* on peut renvoyer ces deux valeurs sous la forme
                       * d'une std::pair
                       */
                      return std::make_pair(value, str);
                  }

                  En quoi est-ce intéressant? me demanderas tu...

                  He bien, en deux mots, c'est intéressant du fait que je peux donner à la fonction un nom qui exprime ce qu'il fait (elle extrait un entier et une valeur) sans "trop entrer dans les détails" (comprend: sans renvoyer une structure qui porte, ainsi que les éléments qui la compose, forcément un nom réducteur en terme d'utilisation).

                  Ainsi, selon le contexte, je pourrai utiliser cette fonction sous une forme proche de

                  int main(){
                      auto [age, name] = extractIntAndString();
                      std::cout<<"hello "<<name<<" you are "<<age 
                               <<" years old\n";
                      
                  }

                  si je considère que je récupère des informations relatives au nom et  l'age d'une personne, mais je pourrais aussi l'utiliser sous la forme de

                      auto [poids, nom]=extractIntAndString();
                      std::cout<< poids<<" pese "<<nom 
                               << " kg... Pas mal pour un chien!!!\n";

                  si j'estime que je récupère les informations relatives au nom et au poids d'un chien.

                  Bien sur, j'aurais pu utiliser deux structures proches de

                  struct Child{
                      int age;
                      std::string name;
                  };
                  struct Hound{
                      int weight;
                      std::string name;
                  };

                  Mais, bien que les deux structures soient composées du même nombre de données et que ces données soient du même type, je n'aurais jamais pu passer de l'un à l'autre, parce qu'il n'y a décidément aucun lien -- réel ou subjectif -- entre la notion d'enfant et celle de chien ;)

                  En m'évitant de créer ces deux structures, j'ai pu rester plus "générique", et j'ai pu constater que seul le contexte avait de l'importance ;)

                  • Partager sur Facebook
                  • Partager sur Twitter
                  Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait

                  Structure avec attributs publics

                  × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                  × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                  • Editeur
                  • Markdown