Partage
  • Partager sur Facebook
  • Partager sur Twitter

Liste d'initialisation et conditions/setters

Sujet résolu
    23 mai 2015 à 17:17:44

    Bonjour à tous,

    Il y a déjà beaucoup de sujets de personnes demandant l'intérêt de la liste d'initialisation. Ce n'est pas mon cas je l'ai bien compris. 

    Vu que on se fait taper sur les doigts par nos cher gourou du c++ quand on ne l'utilise pas ma question est quelle est la bonne facon de faire quand on a quelque chose comme ca 

    Foo(int nb) {
       setNb(nb)
    }
    
    void setNb(int nb) {
       if(nb < 400)
         nb = 42;
    
       m_nb = nb;
    } 

    Nos setters sont pour la plupart du temps la pour une bonne raison du coup j'ai toujours envie de les utiliser directement dans le constructeur non ? Mais du coup ben la moitié des variables chez moi ne passeraient pas par la liste d'initialisation du coup. J'ai loupé quelque chose ?

    Merci d'avance :)

    • Partager sur Facebook
    • Partager sur Twitter
      23 mai 2015 à 17:30:56

      C'est simple : on ne fait pas de setters :D
      • Partager sur Facebook
      • Partager sur Twitter
      La flemme vaincra.
        23 mai 2015 à 17:33:03

        la je crois que j'ai besoin que tu développes un peu zeFresk ^^ depui quand ou quand comment pourquoi ? :)
        • Partager sur Facebook
        • Partager sur Twitter
          23 mai 2015 à 17:48:30

          HS : Il n'y a pas de définition formelle (je crois) à ce qu'est un accesseur, mais pour moi, c'est un simple accès à une variable membre, avec ou sans test des conditions et invariants de classes. Typiquement, cela peut dire que l'on peut écrire :

          void testAccesseur(int value) {
            Foo f;
            f.setNb(value);
            assert(f.getNb() == value);
          }

          Ce qui n'est pas le cas ici.

          HS2 : est ce que c'est le boulot de setNb de vérifier la valeur ? Est-ce que ça ne serait pas plutôt une erreur de programmation, si la valeur invalide arrive jusque là ? (elle devrait plutôt être vérifiée lors de la saisie ou de la lecture d'un fichier par exemple).

          HS3 : les accesseurs sont généralement une violation de Demeter, dû à une mauvaise compréhension de l'encapsulation.

          Pour revenir à la question, oui, bien sûr que si on doit appeler une fonction membre, on devra le faire dans le corps du constructeur et pas dans la liste d'initialisation.

          -
          Edité par gbdivers 23 mai 2015 à 17:49:29

          • Partager sur Facebook
          • Partager sur Twitter
            23 mai 2015 à 18:31:50

            Merci beaucoup de ton intervention gbdivers (même tes HS m'ont fait comprendre des choses et ne sont donc pas si HS que ca :) )

            Pour le HS2 : c'est un cas inventé bidon pour l'exemple mais j'aurais tendance à te dire que pour moi oui puisqu'on m'avait toujours appris que le boulot du setter etait de justement éviter d'assigner n'importe quoi à une variable en faisant ce genre de controles et que du coup le assert de ton exemple ne passe pas toujours. Ca me perturbe un peu puisque du coup je ne vois finalement plus leur intéret puisque dans ce cas j'ai limpression que mettre la variable public et ne plus avoir d'encapsulation sur celle-ci reviendrait au même.

            Donc finalement pour toi si l'on en vient à vouloir utiliser un setter de ce genre dans un constructeur c'est plutôt une erreur de conception dans le programme. 

            Tu me perturbes mais ca m'intéresse grandement ^^.

            • Partager sur Facebook
            • Partager sur Twitter
              24 mai 2015 à 12:41:02

              Soit votre classe est un simple stockage de données et généralement, il est plus simple d'utiliser directement les champs.

              Soit votre classe offre des services et l'utilisateur de cette classe doit se conformer à une API qui ne donne aucune indication sur comment la classe implémente son service. Il est donc alors peu probable que la connaissance de champs internes de la classe soit une bonne idée.

              • Partager sur Facebook
              • Partager sur Twitter
              Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                24 mai 2015 à 13:15:59

                Prenons un exemple concret : un véhicule (pour faire plaisir à notre koala préféré :) )

                Tu vas peut être avoir un membre "réservoir d'essence", qui contient la quantité d'essence dans le réservoir.

                Pour lorsque tu vas mettre de l'essence dans le réservoir, cela ne va pas simplement changer la variable membre, mais également changer le poids du véhicule, casser le moteur si tu mets la mauvaise essence, permettre de rouler plus vite si tu mets de l'essence avec additifs, etc. C'est la différence fondamentale entre "modifier une variable membre" (setter) et rendre un service en conception objet.

                Bien sur, si tu n'as pas toutes ces fonctionnalités à gérer dans ta classe, tu vas peut être au final te retrouver avec un code proche de ton "setNb", qui ne fait que vérifier que tu ne dépasses pas la capacité du réservoir et qui modifie simplement ta variable membre.

                Il est probable que l'on préférera dans ce cas un fonction "addEssence" plutôt que "setEssence", pour éviter d'utiliser cette fonction à mauvais escient. Par exemple, on pourrait être tenté de calculer la distance parcouru par le véhicule, multiplier par la consommation kilométrique et faire un setEssence pour modifier la quantité d'essence. Et là, très clairement, on pense le véhicule en termes d'ensemble de variables et non en tant que services rendus.

                Le problème au final n'est pas les setter/getter. Ce sont des fonctions qui réduisent la qualité du code (couplage plus fort entre interface et implémentation des classes), mais cela peut être un choix volontaire de la part du développeur. Le problème est surtout la "mauvaise" façon de concevoir ses classes, l'utilisation de accesseurs qui en découle, et le fait que faire cela par défaut, sans comprendre les conséquences sur la qualité du code.

                • Partager sur Facebook
                • Partager sur Twitter
                  25 mai 2015 à 10:30:58

                  Merci beaucoup à vous.

                  Ton exemple m'a bien éclairé sur le conception en général sur les setters et autres fonctions de service :)

                  Pour conclure alors avant de passer en résolu, gardons notre voiture. Celle-ci à sa construction doit initialiser son réservoir d'essence, disons qu'il n'y a pas de trou noir dedans et donc qu'il ne peut être négatif :)

                  C'est quand même bien à mon constructeur de se défendre et de lever une exception ou dans mon cas plutôt préférer le laisser juste vide et ainsi devoir me manger une condition dans le corps de mon constructeur (et ne pas utiliser la liste) ?

                  -
                  Edité par maximeengel 25 mai 2015 à 11:03:43

                  • Partager sur Facebook
                  • Partager sur Twitter
                    25 mai 2015 à 11:03:38

                    Dans la logique, c'est selon ton avis. Si pour toi une voiture ne peut pas avoir un réservoir négatif et que si c'est le cas c'est pas bon, alors oui tu peux lancer une exception qui empêchera la construction d'un objet aux caractéristiques non valides. Si pour toi tu te dis "c'est l'utilisateur qui fait le crétin", tu pose une assertion dans le constructeur.

                    • Partager sur Facebook
                    • Partager sur Twitter
                      25 mai 2015 à 14:10:40

                      Une création de voiture avec réservoir négatif, c'est une erreur de programmation. Donc je pense qu'on devrait claquer une bonne vieille assertion, pas lancer une exception.
                      • Partager sur Facebook
                      • Partager sur Twitter
                      Mehdidou99 - Plus on apprend, et euh... plus on apprend. | Ne lisez pas le cours de C++ d'OpenClassrooms ! Il est de mauvaise qualité. Essayez plutôt celui-là. Jeux (3D) en C++ ? Unreal Engine 4, c'est bien, mangez-en !
                        25 mai 2015 à 15:08:51

                        maximeengel a écrit:

                        Pour conclure alors avant de passer en résolu, gardons notre voiture. Celle-ci à sa construction doit initialiser son réservoir d'essence, disons qu'il n'y a pas de trou noir dedans et donc qu'il ne peut être négatif :)

                        C'est quand même bien à mon constructeur de se défendre et de lever une exception ou dans mon cas plutôt préférer le laisser juste vide et ainsi devoir me manger une condition dans le corps de mon constructeur (et ne pas utiliser la liste) ?

                        Donc tu considères que si le volume dans le réservoir d'essence n'est pas correct, la voiture ne doit pas être construite, il faut la détruire ? J'espère que tu ne bosses pas pour un fabriquant de voiture, tu vas leur coûter cher :D

                        Le rôle d'un constructeur est de mettre l'objet dans un état valide. Rien de plus. La construction d'un objet ne devrait échouer que si l'état interne n'est pas valide.

                        Pour une voiture, c'est quoi l'état valide ? 

                        Dans un cas, ça sera valide après avoir monté la carrosserie. Le reste (monter les sièges, mettre le peinture sur la carrosserie, etc) sera considéré comme de la personnalisation, à la liberté du client.

                        A l'autre extrême, une voiture est "finie" quand elle est prête à partir en vacances, avec les valises dans le coffre, les enfants avec leur ceinture de sécurité et l'auto-radio allumé.

                        Si tu fais un jeu de gestion de garage automobile, tu auras un voiture avec un constructeur qui te permet pas de remplir le réservoir (puisqu'il n'y aura pas de réservoir à ce moment là). Si tu fais un jeu de gestion de voyage, ton constructeur te donnera un voiture prête à partir, réservoir plein.

                        Il n'y a pas de limite haute à ce que tu peux faire dans le constructeur (il y a juste une limite basse = avoir un état valide). C'est une question de choix de design. Plus tu mettras de fonctionnalités dans le constructeur, plus il sera facile de construire un objet en une seule étape, mais plus ton interface sera lourde. Et la construction de ton objet risque d'échouer pour un paramètre accessoire.

                        Tu vas donc avoir 2 solutions pour remplir ton réservoir : dans le constructeur ou à côté. Cela donne cela :

                        vehicule vroumvroum(40); // remplit le réservoir avec 40L
                        
                        // ou
                        
                        vehicule supervroumvroum;
                        supervroumvroum.addEssence(40);

                        La première version permet de gagner une ligne, mais est-ce réellement important ?

                        Par contre, il est clair que dans les 2 cas, remplir le réservoir passera par la fonction addEssence. On évite d'écrire 2 fois le même code, donc on ne va pas écrire le code de vérification de la quantité d'essence dans le constructeur et dans addEssence. Donc le constructeur appellera addEssence.

                        Bref, dans les 2 cas, tu ne peux pas initialiser le réservoir dans la liste d'initialisation et tu dois passer pas addEssence. Mais la question importante est : est-ce que tu dois proposer un constructeur qui permet de remplir le réservoir ?

                        • Partager sur Facebook
                        • Partager sur Twitter
                          25 mai 2015 à 15:17:41

                          Ou un nom signé. Difficile de faire plus explicite ;) Il faut mettre des types qui lèvent un maximum d’ambiguïtés.

                          Dans la logique, je suis d'accord avec @mehdidou99: assertion. Si l'utilisateur veut une exception, il le ferra avant d’appeler le constructeur.

                          • Partager sur Facebook
                          • Partager sur Twitter
                            25 mai 2015 à 19:01:35

                            Merci à tous et merci beaucoup gbdivers tu as mis le doigt finalement sur ma question qui était en fait sur le bout de ma langue : c'est quoi finalement la philosophie d'un constructeur. Ton dernier post est la réponse absolue. A priori on peut maintenant juste voter et pas mettre en avant la réponse qui a résolu comme y a un moment ? ou je suis aveugle.
                            • Partager sur Facebook
                            • Partager sur Twitter
                              25 mai 2015 à 19:08:01

                              maximeengel a écrit:

                              A priori on peut maintenant juste voter et pas mettre en avant la réponse qui a résolu comme y a un moment ?

                              Peut importe. C'est une réponse qui a déjà été donné et qui sera encore donné. Personne ne lis les anciennes discussions (l'outil de recherche sur le forum n'aide pas trop, quand je recherche un ancien de mes messages, je le trouve pas. Mais sur Dvp, qui a un outil de recherche un peu mieux, les mêmes questions sont reposées 100 fois). Donc moi (ou un autre) expliqueront à nouveau au prochain qui demandera :)
                              • Partager sur Facebook
                              • Partager sur Twitter

                              Liste d'initialisation et conditions/setters

                              × 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