Partage
  • Partager sur Facebook
  • Partager sur Twitter

Accesseurs, quelle utilité ?

    14 janvier 2018 à 19:30:02

    Bonjour. Quelle est l'utilité d'encapsuler des données et d'ajouter en plus des méthodes .get_truc() .set_truc() alors qu'il me suffit de laisser les membres publiques ? J'ai fait des petites choses avec les deux méthodes (des vecteurs, des déplacements de cercles, de rectangles, de formes...), je trouve que les données privées alourdissent considérablement l'écriture pour un gain non évident.
    • Partager sur Facebook
    • Partager sur Twitter
      14 janvier 2018 à 20:32:30

      Salut,

      Le truc, c'est qu'il faut envisager de concevoir les classes comme des fournisseurs de services, et non comme des fournisseurs de données.  Les données membres d'une classe n'étant alors présentes que... pour permettre à la classe de fournir les services que l'on attend de sa part.

      Les accesseurs et mutateurs sont apparus parce qu'il a fallu "un peu de temps" avant de se rendre compte de cette erreur (pour beaucoup, au début de l'orienté objet, une classe n'était ... qu'un agrégat de données).

      La notion d'accesseurs (get_truc) peut avoir un intérêt, à partir du moment où l'on peut considérer le fait de fournir l'accès à une information fait partie des services que la classe doit pouvoir rendre.

      Ainsi, si tu veux concevoir une classe "personne", tu vas partir du principe qu'elle va devoir rendre "un certain nombre de services", comme

      • pouvoir répondre à la question "quel est ton nom?"
      • pouvoir répondre à la question "quel est ton prénom?"
      • pouvoir obéir à l'ordre "déménage à <telle adresse>"
      • un ou deux autres services supplémentaires

      Pour que la classe puisse répondre aux deux questions indiquées, il faut bien lui fournir des fonctions qui -- on peut l'imaginer -- se contenteront sans doute de renvoyer la valeur d'une donnée particulière, et qui sont donc des accesseurs "purs et durs".

      De plus, il faut se dire que, même les accesseurs ne sont intéressant que s'ils permettent, effectivement, de répondre à un besoin particulier.  Ainsi, si l'on voulait concevoir une classe voiture, il ne faut pas vraiment avoir fait d'études de mécaniques pour savoir qu'elle va disposer d'un réservoir à carburant.  Mais, à moins d'être mécanicien et d'avoir une bonne raison pour changer le réservoir, nous n'avons absolument aucune raison de vouloir y accéder à partir de la voiture.

      Par contre, on s'attend à ce que la voiture nous fournisse "un certain nombre" de services relatifs à la présence de ce réservoir, comme

      • le fait de pouvoir le remplir (au travers de la trappe à carburant)
      • le fait de connaître son niveau de remplicage (au travers de la jauge à carburant)
      • (éventuellement) le fait de connaitre le nombre de kilomètres que l'on peut encore effectuer (sur base d'une évaluation de la quantité de carburant dont le réservoir dispose et de la consommation actuelle)
      • ... d'autres informations auxquelles je ne pense pas maintenant

      On se rend donc compte qu'il n'y a absolument aucune raison de proposer une fonction get_reservoir(), car la présence même du réservoir est ... rendue "transparente" grâce aux services qui ont trait à cette présence.

      Les choses sont, par contre, beaucoup plus claires pour ce qui concerne les mutateurs (set_xxx), car, de manière générale, c'est toujours une très mauvaise idée de les fournir:

      D'abord, il n'y aurait aucune raison pour qu'une personne nommé Jean Durant n'en vienne à s'appeler Jaques Dupont.  Et, bien sur, si tu n'as déjà pas fourni d'accesseur sur une donnée (comme c'est le cas pour le réservoir), tu n'as de toute évidence aucune raison de fournir un mutateur sur cette donnée.

      Mais en plus, il faut comprendre que les données d'une classe sont systématiquement soumises à certaines règles qui devront être respectées si l'on veut que la classe puisse agir "comme on est en droit de s'y attendre". Or, la présence d'un mutateur va obliger l'utilisateur de notre classe à prendre lui-même "toutes les précautions" pour que la valeur (qu'il devra calculer "par lui-même")  qu'il souhaite donner à notre classe soient... cohérentes.

      La loi de Finagle aidant, tu peux te dire qu'il y a "toutes les chances" pour qu'il oublie de prendre une des règles en compte, à un moment ou à un autre. Et ce, même si ces règles sont écrites en gros et en fluo juste à coté de lui ;)

      Or, comme disait l'autre

      Il faut faire en sorte de rendre l'utilisation correcte de notre classe aussi simple que possible, et de rendre l'utilisation incorrecte de celle-ci aussi difficile que possible

      Et, pour ce faire, il n'y a pas de miracle: nous devons fournir des fonctions qui se chargeront de vérifier que "toutes les règles" sont respectées avant de modifier une donnée de la classe

      Ainsi, nous préférerons avoir une fonction move(distanceX, distanceY) ou moveTo(newPosition) qui pourra s'assurer que la position de destination ne fait pas entrer notre personnage dans un mur qu'une fonction set_position(newPosition) qui obligera l'utilisateur de la classe à calculer lui-même la position de destination, au risque qu'il oublie de prendre en compte le fait qu'elle est occupée par un feu, un mur ou n'importe quel autre obstacle.

      Ceci étant dit: toute donnée qui est accessible à l'utilisateur d'une classe risque d'être modifiée de manière incongrue par l'utilisateur, et cela peut poser pas mal de problèmes (je viens de les expliquer, je ne vais pas recommencer ;) )

      C'est la raison pour laquelle il est effectivement préférable que les données soient dans l'accessibilité privée.

      On peut d'ailleurs aller plus loin en disant que les données ne peuvent être que dans l'accessibilité privée, y compris si elle doivent être accessibles aux classes dérivées.  Seules des fonctions devraient être présentes dans l'accessibilité protégée ou publique (selon les besoins) ;)

      -
      Edité par koala01 14 janvier 2018 à 20:38:21

      • 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
        14 janvier 2018 à 20:54:05

        Merci beaucoup de ta réponse, mais je trouve que c'est un peu bancal (pas ta réponse mais l'idée même d'objet). Si les classes doivent fournir des services, alors où se trouvent les données sur et à partir desquelles ces services s'appliquent ?

        Autre problème, si tu me réponds que les données sont dans la classe qui les utilise, alors le problème c'est qu'une même donnée peut faire l'objet de plusieurs services différents (et devrait donc appartenir à plusieurs classes) ou alors il faut qu'une seule classe rendent plusieurs services (et tu retrouves avec des classes énormes). Un moment j'essayais de régler ca à coup d'amitié (à la fin tout le monde est ami avec tout le monde et on ne voit pas pourquoi ne pas tout mettre en public). Ou bien à coup d'héritage, mais on se retrouve avec des choses illogiques... En réalité dans un jeu tu ne peux pas avoir une classe "personnage", soit tout ton jeu est dedans, soit il est ami avec tout le reste et il n'y a plus d'encapsulation. Et c'est logique, puisque jouer c'est essentiellement interagir avec un environnement...

        En fait j'ai même essayé de faire un petit moteur de jeu, et la logique de penser "services" et non "données" m'a conduit à créer des classes "vies", "position", "taille", "mana", "force", "inventaire" etc... Ces classes sont donc transversales par rapport aux objets auxquels on pense naturellement ("joueur", "coffre", "arbre", "monstre"). Le problème c'est qu'il y a beaucoup de manières possibles de modifier une vie ou une position, ces classes doivent donc fournir une multitude de services... Pas évident.

        -
        Edité par Umbre37 14 janvier 2018 à 21:18:28

        • Partager sur Facebook
        • Partager sur Twitter
          14 janvier 2018 à 21:07:36

          Hello,

          C'est pour ca que l'orienté objet est très mauvais pour créer des jeux.

          Tu as des patterns et des concepts bien plus interessant.

          Prenons l'exemple des races et des classes dans un jeu.

          Un nain peux etre un chasseur et un guerrier, un humain un guerrier et un mage.

          Si ton reflexe est de faire une classe Personnage, avec Humain et Nain qui héritent de Personnage, puis ensuite Chasseur et Guerrier de Nain et Guerrier et Mage de Humain, tu te retrouver à dupliquer des données de guerrier pour les deux classes.

          La tu vas me dire, une solution est un heritage multiple, mais la je vais te répondre que c'est déja bien plus complexe, et en plus pas très pratique.

          Si on part ensuite sur Paladin, Chaman, Barbare, Barde et autres, tu auras beaucoup de choses à CODER, sauf que ce n'est pas forcement des caracteristiques codables, ton game designer ne code pas forcement.

          Il faut du coup un système plus fluide que du pur OO.

          Par exemple tu peux partir sur de l'ECS, qui est assez simple à mettre en place un fois que tu as compris les defauts de l'OO pour de tel projets.

          Un exemple est expliqué ici: http://guillaume.belz.free.fr/doku.php?id=ecs

          • Partager sur Facebook
          • Partager sur Twitter

          Architecte logiciel - Software craftsmanship convaincu.

            14 janvier 2018 à 21:07:47

            En règle générale, il est de bonne pratique d'avoir des get() et set() ("mutateurs") pour toutes les données auxquelles tu veux pouvoir accéder car tu pourrais avoir envie d'exécuter un comportement à chaque fois qu'une donnée est modifiée / lue sans avoir à le faire (et surtout te souvenir de le faire) à chaque fois que tu veux modifier / lire une donnée. Un exemple évident est la vie : tu veux que à chaque fois que la vie est baissée, elle soit vérifiée et provoque la mort du personnage si elle est en dessous / égale à 0. Sans set(), tu n'as JAMAIS la garantie que cela sera vérifié quand la vie sera modifiée (quelqu'un pourrait juste faire vie = vie - 1 et s'en tenir à là).
            • Partager sur Facebook
            • Partager sur Twitter
              14 janvier 2018 à 21:12:09

              Hoshiqua a écrit:

              En règle générale, il est de bonne pratique d'avoir des get() et set() ("mutateurs") pour toutes les données auxquelles tu veux pouvoir accéder car tu pourrais avoir envie d'exécuter un comportement à chaque fois qu'une donnée est modifiée / lue sans avoir à le faire (et surtout te souvenir de le faire) à chaque fois que tu veux modifier / lire une donnée. Un exemple évident est la vie : tu veux que à chaque fois que la vie est baissée, elle soit vérifiée et provoque la mort du personnage si elle est en dessous / égale à 0. Sans set(), tu n'as JAMAIS (ah bon?) la garantie que cela sera vérifié quand la vie sera modifiée (quelqu'un pourrait juste faire vie = vie - 1 et s'en tenir à là).


              C'est du troll?

              Avec les mutateurs déjà tu t'assure d'avoir ton objet dans un état incorrect et en plus tu n'as pas la sureté d'avoir l'information que ton personnage soit mort, contrairement à ce que tu dit.

              Avec une toute petite connaissance des patterns, on vois par exemple le pattern visitor, qui peux prévenir une classe qu'une autre change d'état, avec les données qui vont avec.

              • Partager sur Facebook
              • Partager sur Twitter

              Architecte logiciel - Software craftsmanship convaincu.

                14 janvier 2018 à 21:16:17

                Hoshiqua a écrit:

                En règle générale, il est de bonne pratique d'avoir des get() et set() ("mutateurs") pour toutes les données auxquelles tu veux pouvoir accéder car tu pourrais avoir envie d'exécuter un comportement à chaque fois qu'une donnée est modifiée / lue sans avoir à le faire (et surtout te souvenir de le faire) à chaque fois que tu veux modifier / lire une donnée.

                Un peu de lecture : https://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html

                Si les getters sont régulièrement justifiés, les setters le sont nettement moins. La présence de setters et de getters témoignent souvent d'une absence de conception et d'un développeurs qui pensent ses classes en termes de données et non en terme de services : c'est tout l'anti-thèse de l'orienté objet. Par exemple tu nous cites :

                Hoshiqua a écrit:

                Un exemple évident est la vie : tu veux que à chaque fois que la vie est baissée, elle soit vérifiée et provoque la mort du personnage si elle est en dessous / égale à 0. Sans set(), tu n'as JAMAIS la garantie que cela sera vérifié quand la vie sera modifiée (quelqu'un pourrait juste faire vie = vie - 1 et s'en tenir à là).

                C'est typiquement une conception mal pensée. Avoir un getter est cohérent (même si ce n'est pas nécessairement indispensable, ça dépend du niveau de connaissance qu'on veut en donner à l'utilisateur). Avoir un setter est une catastrophe. On préférera très largement équiper la classe avec une fonction membre blesser et une fonction membre soigner qui représente une service plus précis et introduise un niveau d'abstraction permettant justement de cacher le fonctionnement interne de la classe.

                A l'inverse : le setter et le getter n'abstraient absolument rien de la classe et pire : exposent le fonctionnement de la classe.

                • Partager sur Facebook
                • Partager sur Twitter

                Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

                  14 janvier 2018 à 21:31:01

                  @Hoshiqua, l'exemple que tu as choisi est un des meilleurs pour démontrer à quel point les setters sont une catastrophe.

                  Avec le design que tu proposes, n'importe qui peut dire: "maintenant, c'est ça tes points de vie". Sans prendre en compte que le destinataire peut avoir des résistances, des protections, des immunités, etc. Le bon service est "encaisse(degats);" Les dégâts étant une  combinaison de quantités et de natures associées (poison, électrique, feu, froid...). A charge au destinataire de se mettre à jour comme il se doit.

                  Bref, faire un couple getter + setter par donnée, est le pire design possible. Enfin. Il faut avoir conscience que c'est un désign qui n'a rien d'OO. C'est au contraire de l'orienté données qui cherche à se déguiser en orienté objets, ce qui est très sournois.

                  J'ai la flemme de ressortir mes liens sur le sujet. google "Why setters are evil?" est un bon début.

                  BTW, si un setter ne "sette" pas sans rechigner, c'est que le nom de la fonction n'est pas bon, et que c'est un mensonge. C'est mal de mentir au code client. Il ne va pas comprendre ce qu'il se passe après.

                  @necro211, l'OO va servir pour designer des ECS. Mais effectivement, associer une classe personnage ou une race à une classe OO ne marche pas bien. On perd vite en flexibilité. Comment évoluer? Se multiclasser ? Comment éviter de faire exploser le nombre de classes finales, etc.

                  • 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.
                    14 janvier 2018 à 21:34:26

                    Umbre37 a écrit:

                    Bonjour. Quelle est l'utilité d'encapsuler des données et d'ajouter en plus des méthodes .get_truc() .set_truc() alors qu'il me suffit de laisser les membres publiques ? J'ai fait des petites choses avec les deux méthodes (des vecteurs, des déplacements de cercles, de rectangles, de formes...), je trouve que les données privées alourdissent considérablement l'écriture pour un gain non évident.


                    Pour des petites choses, effectivement, ça alourdit terriblement l'écriture. Exemple typique, une Position avec un x et un y.

                    Il faut voir aussi que les exemples "classiques" qui prétendent expliquer la prog OO sont complètement pourris et pas représentatifs : ils envoient les débutants dans une très mauvaise direction.

                    Prenons l'exemple d'un cercle qui est une forme, parait-il. dans un sens c'est vrai abstrait mais bon, la programmation objets, ça parle pas d'abstractions platoniciennes,  mais de code qu'on veut faire tourner pour que ça rende certains services à ceux qui l'emploient.

                    Donc je repose la question : quel est le rapport entre les services offerts par une forme, et ceux offerts par un cercle ?   Ben pour l'instant on n'en sait rien. Il faut bien plus préciser dans quel domaine on a besoin des codes correspondants.  Parce que ce n'est pas du tout pareil de demander à les dessiner dans la fenetre ou bien, par exemple de demander quelle est la  forme (surface) qui correspond à l'intersection de deux cercles. Donc en fait on a pipeauté le débutant en lui laissant croire que la liste des services d'une forme découlait de l'impression vague qu'il en avait "dans le monde réel" (qui n'existe pas).

                    Ca se voit aussi sur  "les chefs et les employés" sont deux sous-classes de Personnel.  Ah ouais.  et alors l'objet Employé, il lui arrive quoi quand il devient chef ?  De toute évidence, ça ne colle pas du tout si le but de la modélisation, c'est de représenter un personnel qui est évidemment en évolution perpétuelle.

                    Maintenant, les objets, ça n'a pas été inventé pour ça. C'est fait pour penser à un découpage des programmes  en "modules" qui ont une API (les fonctions qu'on peut leur demander de faire) bien claire. Et que savoir comment c'est fait dedans, c'est pas les oignons de ceux qui ne font qu'utiliser les API, et sont heureux de pouvoir faire **abstraction** des détails internes des modules.

                    La visibilité privé/public/... est un moyen technique offert par C++ (et Java, C# etc) de séparer ce qui est public de ce qui est interne, le compilateur étant là pour vérifier qu'on ne tripote pas les trucs qu'on doit laisser tranquille. C'est une protection.

                    Mais évidemment, quand on commence par présenter les objets en disant que c'est des données et des fonctions regroupées, et que t'as pas JAMAIS le droit de toucher aux données directement PARCE QUE, et que tu dois TOUJOURS écrire des fonctions, en plus dans des exemples minables où c'est vraiment pas concluant , on comprend que ça génère de la frustration.

                    -
                    Edité par michelbillaud 14 janvier 2018 à 21:49:25

                    • Partager sur Facebook
                    • Partager sur Twitter
                      14 janvier 2018 à 21:37:51

                      Bon j'ai une idée (c'est peut-être proche d'un ECS je sais pas).

                      Si je mets toutes les données dans des std::map<ID, T>

                      Par exemple std::map<ID, unsigned int> vie;

                      Chaque truc de mon jeux a un ID et peut donc avoir une vie qui sera rangée avec toutes les autres dans la map.

                      A chaque tour de boucle je fais un truc du genre appliquer un foncteur tuer_les_morts(ID) sur ma map pour tuer tout ce qui a une vie négative.

                      J'étends ce principe pour toutes les données et tous les mécanismes du jeu.

                      Ca me semble assez souple comme solution.

                      Qu'en pensez-vous ?

                      -
                      Edité par Umbre37 14 janvier 2018 à 22:05:52

                      • Partager sur Facebook
                      • Partager sur Twitter
                        14 janvier 2018 à 21:50:34

                        lmghs a écrit:

                        @necro211, l'OO va servir pour designer des ECS. Mais effectivement, associer une classe personnage ou une race à une classe OO ne marche pas bien. On perd vite en flexibilité. Comment évoluer? Se multiclasser ? Comment éviter de faire exploser le nombre de classes finales, etc.


                        Oui, et c'est bien en comprenant les faiblesses de l'OO que l'on arrive à faire un bon ECS.

                        Dans l'exemple de gbdivers il utilise des classes, mais c'est tout à fait faisable en fonctionnel par exemple.

                        L'ECS ce n'est que de la composition, et c'est ce qui le rend cool, car simple à faire grossir, avec peu de couplage.

                        • Partager sur Facebook
                        • Partager sur Twitter

                        Architecte logiciel - Software craftsmanship convaincu.

                          14 janvier 2018 à 22:20:25


                          michelbillaud a écrit:

                          La visibilité privé/public/... est un moyen technique offert par C++ (et Java, C# etc) de séparer ce qui est public de ce qui est interne, le compilateur étant là pour vérifier qu'on ne tripote pas les trucs qu'on doit laisser tranquille. C'est une protection.

                          Est-ce qu'on ne peut pas simplement faire des fonctions qui répondent à la demande de l'utilisateur, par exemple, voiture.demarrer_moteur() ; voiture.tourner_a_droite(); et laisser les données publiques, et si quelqu'un veut faire voiture.moteur = ; (acceder à des paramètres internes) c'est son problème. Ainsi une classe qui serait faite pour modifier un moteur (un garagiste par exemple) le pourrait sans difficulté. L'utilisateur lui prend les risques qu'il veut prendre (c'est toujours pareil au fond)

                          -
                          Edité par Umbre37 14 janvier 2018 à 22:29:06

                          • Partager sur Facebook
                          • Partager sur Twitter
                            15 janvier 2018 à 0:37:21

                            Umbre37 a écrit:

                            Merci beaucoup de ta réponse, mais je trouve que c'est un peu bancal (pas ta réponse mais l'idée même d'objet). Si les classes doivent fournir des services, alors où se trouvent les données sur et à partir desquelles ces services s'appliquent ?

                            En tant qu'utilisateur de la classe, tu n'as pas à t'en inquiéter, parce que ce n'est qu'un "sordide détail d'implémentation".

                            La plus grosse difficulté résidant du coup dans le fait de se mettre en tête que l'on n'est le développeur d'une classe que... le temps d'écrire le code des fonctions qu'elle expose, et que, dés que l'on a fini d'écrire ce code, et qu'il s'agit d'utiliser la classe, nous en devenons... un simple utilisateur

                            Je ne sais pas si tu connais un tout petit peu le C, mais c'est exactement le même principe que pour les types opaques que les développeurs C apprécient tellement.

                            Regarde la structure FILE, par exemple: on n'est pas sensé savoir de quoi elle est composée, mais on dispose de "suffisamment" de services aux travers des fonctions fopen, fread, fwrite et fclose pour pouvoir l'utiliser correctement.

                            Ici, le principe est exactement le même ;)

                            Autre problème, si tu me réponds que les données sont dans la classe qui les utilise, alors le problème c'est qu'une même donnée peut faire l'objet de plusieurs services différents (et devrait donc appartenir à plusieurs classes) ou alors il faut qu'une seule classe rendent plusieurs services (et tu retrouves avec des classes énormes).

                            C'est là qu'interviennent des notions comme le SRP (Single Responsabilty Principle ou principe de la responsabilité unique)ou l'ownership (la "propriété") d'une donnée:

                            Le SRP nous dit que chaque type, chaque fonction et chaque donnée que tu crée ne doit poursuivre qu'un et un seul objectif.

                            Appliqué aux fonctions, il implique que, plutôt de coder une fonction qui contient tout le code pour

                            • faire le café
                            • faire la vaisselle
                            • faire la laissive
                            • repasser le linge et
                            • sortir le chien

                            tu devrais créer une fonction particulière pour faire chacune de ces actions, ce qui te permettra, non seulement de tester chacune des actions séparément, mais aussi de les combiner dans l'ordre que tu veux.

                            Appliqué aux types de données (et donc ... au classes), il implique déjà d'éviter des termes comme "manager" ou "gestionnaire", parce que ce sont des termes qui sont beaucoup trop générique, et qui vont forcément tendre vers un nombre beaucoup trop important de responsabilités.

                            A la place, on va créer des classes qui ne prennent qu'une partie du problème en charge, par exemple:

                            • une fabrique, pour construire les objets dont on a besoin
                            • un Holder (ou un Owner) pour posséder les objets créés (et nous y donner accès)
                            • un (ensemble de) manipulateur(s) pour pouvoir manipuler les objets créés (en veillant à ce que chaque manipulation soit "la plus simple possible", quitte à ce qu'elle fasse appel à... d'autres manipulateurs)
                            • D'autres éléments peuvent faire leur apparition ;)

                            Appliqué aux données, il implique que, si tu décide qu'une donnée (nommons la A) représente la longueur d'un rectangle, il n'est pas question de décider en cours d'utilisation qu'elle puisse représenter la largeur de ce rectangle.

                            Donc, oui, une donnée peut être utilisée dans le cadre de plusieurs services, mais les services que peut rendre une classe doivent être limité à une responsabilité bien particulière.

                            La propriété des éléments permet  quant à elle principalement de décider qui a "droit de vie et de mort" sur la donnée. Généralement, on essayera de faire en sorte qu'il n'y ai qu'un seul élément qui puisse décider de détruire une donnée, et ce, même si la donnée est susceptible d'être utilisée par "de nombreux" éléments qui en ont besoin, que l'on désignera par exemple sous le terme d'utilisateur.

                            L'utilisateur d'une donnée peut... l'utiliser.  Dans certains cas, il peut la modifier, quoi que l'on essayera généralement de limiter au maximum le nombre d'utilisateurs qui ont le droit de le faire. Idéalement, la plus grande majorité des utilisateurs devrait se contenter... d'un accès "en lecture seule".

                            En aucun cas, un utilisateur ne pourra prendre la décision de détruire la donnée qu'il utilise.  Ca, c'est une décision qui ne peut être prise que par le propriétaire de la donnée.

                            Dans quelques cas, il se peut que plusieurs éléments puissent prétendre à la propriété d'une même donnée. Nous disposons, le cas échéant, de solutions nous permettant d'avoir plusieurs propriétaires.  Mais ces solutions ne doivent être utilisées qu'en "dernier recours".

                            Un moment j'essayais de régler ca à coup d'amitié (à la fin tout le monde est ami avec tout le monde et on ne voit pas pourquoi ne pas tout mettre en public).

                            Ca, ca dénote un problème de conception, du à un couplage trop important de tes données.

                            Ou bien à coup d'héritage, mais on se retrouve avec des choses illogiques...

                            Là encore, il faut faire attention, car l'héritage est la relation la plus forte qui puisse exister entre deux types de données. C'est donc la relation à utiliser en "dernier ressort", et uniquement si le LSP (Liskov Substitution Principle ou principe de substitution de Liskov)est respecté ;)

                            Notes au passage que le fait de respecter le SRP au niveau des classes (et des interfaces) permet, justement, d'éviter certains "illogismes" auxquels tu as été confronté en utilisant l'héritage de manière aberrante ;)

                            En réalité dans un jeu tu ne peux pas avoir une classe "personnage", soit tout ton jeu est dedans, soit il est ami avec tout le reste et il n'y a plus d'encapsulation. Et c'est logique, puisque jouer c'est essentiellement interagir avec un environnement...

                             A vrai dire, l'approche orientée objet n'est pas du tout adaptée à la création de jeux de type RPG.  Pour cet usage, on préférera sans doute une approche orientée ECS (Entity Component System). Mais pour d'autres raisons (essentiellement l'explosion du nombre de classes et de leur dépendances).

                            Une classe personnage serait tout à fait adaptée dans une approche OO, à la condition de faire en sorte qu'elle n'expose que des services que l'on est en droit d'attendre de la part... du personnage, et qu'elle délègue à d'autres classes les responsabilités qui n'ont rien à voir avec le personnage:

                            un personnage, ca bouge, ca attaque quelque chose, ca décide de parler avec quelqu'un, mais c'est à peu près tout:

                            Le fait de savoir de quel équipement ton personnage dispose, d'acheter ou de vendre de l'équipement, voire, de quel équipement il est actuellement... équipé, ca, c'est de la responsabilité de... son inventaire

                            • Le fait de jouer un son à un moment précis, c'est de la responsabilité... du système de son.
                            • Le fait de savoir comment afficher le personnage et de l'afficher, c'est de la responsabilité ... du système d'affichage
                            • Tout ce qui a trait à la communication entre différent joueur, c'est la responsabilité ... du système de communication

                            Et; bien sur, qui dit "système" sous entend qu'il y a sans doute "plusieurs éléments" qui "travaillent en commun" pour atteindre un objectif complexe, mais que chaque élément ne prend qu'une "petite responsabilité", clairement définie à sa charge ;)

                            En fait j'ai même essayé de faire un petit moteur de jeu, et la logique de penser "services" et non "données" m'a conduit à créer des classes "vies", "position", "taille", "mana", "force", "inventaire" etc... Ces classes sont donc transversales par rapport aux objets auxquels on pense naturellement ("joueur", "coffre", "arbre", "monstre"). Le problème c'est qu'il y a beaucoup de manières possibles de modifier une vie ou une position, ces classes doivent donc fournir une multitude de services... Pas évident.

                            Justement, non...

                            Des classes comme vies, mana ou force ne doivent pas exposer énormément de services. Deux ... Allez: cinq suffisent: add(quantité) et remove(quantité) addMax(quantite), modifyMax(rapport) et une fonction alive (ou enough, pour la force et le mana)

                            Après, il y a sans doute beaucoup de raisons pour faire appel à ces services, mais ca, ce n'est pas de la responsabilité de la classe elle-même: c'est de la responsabilité... de ses utilisateurs ;)

                            L'inventaire est sans doute plus complexe, mais ne nécessite pas pour autant d'exposer un si grand nombre de services ;)

                            Par contre, effectivement, là où l'on va commencer à suer, c'est quand il faudra créer les hiérarchies de classes à base de Equipement, de Monstre ou de Joueur.

                            Non pas parce que ces hiérarchies doivent exposer un grand nombre de services, mais plutôt à cause des combinaisons que l'on peut s'attendre à retrouver dans les hiérarchies

                            Et c'est pour cela que l'on préférera l'approche ECS ;)

                            -
                            Edité par koala01 15 janvier 2018 à 0:59:01

                            • 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
                              15 janvier 2018 à 1:29:35

                              Merci beaucoup pour ta longue réponse, je comprends beaucoup mieux le principe. Du coup je me dirige vers un ECS, même si la conception n'est pas facile.
                              • Partager sur Facebook
                              • Partager sur Twitter
                                15 janvier 2018 à 10:42:20

                                koala01 a écrit:

                                Umbre37 a écrit:

                                Merci beaucoup de ta réponse, mais je trouve que c'est un peu bancal (pas ta réponse mais l'idée même d'objet). Si les classes doivent fournir des services, alors où se trouvent les données sur et à partir desquelles ces services s'appliquent ?

                                En tant qu'utilisateur de la classe, tu n'as pas à t'en inquiéter, parce que ce n'est qu'un "sordide détail d'implémentation".

                                La plus grosse difficulté résidant du coup dans le fait de se mettre en tête que l'on n'est le développeur d'une classe que... le temps d'écrire le code des fonctions qu'elle expose, et que, dés que l'on a fini d'écrire ce code, et qu'il s'agit d'utiliser la classe, nous en devenons... un simple utilisateur

                                En fait il faut prendre le problème par l'autre bout :  tu es le développeur à qui on confie un boulot, qui est de réaliser une classe qui se conforme à une certaine interface de programmation.


                                Par exemple du code pour piloter un véhicule, avec des fonctions avancer(distance) et tourner(angle).  Et ce seront exactement les mêmes fonctions pour piloter une bagnole à un moteur ou un tank qui en a deux.  Ou a un aeroglisseur qui a des ventilateurs. Donc la variable "moteur", tu te la gardes pour toi, elle est liée à une réalisation particulière de la notion abstraite de véhicule qui avance et qui tourne.

                                Normalement, l'interface elle sera décrite par une classe abstraite (en C++) ou mieux, une interface en Java. Comme ça le code qui commande sera polymorphe, complètement indépendant de la façon dont les véhicules font marcher leurs moteurs.

                                -
                                Edité par michelbillaud 15 janvier 2018 à 10:44:13

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  15 janvier 2018 à 11:18:03

                                  Umbre37 a écrit:

                                  Est-ce qu'on ne peut pas simplement faire des fonctions qui répondent à la demande de l'utilisateur, par exemple, voiture.demarrer_moteur() ; voiture.tourner_a_droite(); et laisser les données publiques, et si quelqu'un veut faire voiture.moteur = ; (acceder à des paramètres internes) c'est son problème. Ainsi une classe qui serait faite pour modifier un moteur (un garagiste par exemple) le pourrait sans difficulté. L'utilisateur lui prend les risques qu'il veut prendre (c'est toujours pareil au fond)

                                  -
                                  Edité par Umbre37 il y a environ 12 heures

                                  La, tu poses la question de fonctionalités particulières accessibles par des classes (utilisateurs) particuliers, car pour reprendre ton exemple, qu'est ce qu'un particulier en a à cirer d'accèder au moteur quand cela n'intéresse que le garagiste ?
                                  Dans les cas les plus simples, cela peut être solutionné par les relations d'amitié (ne t'enflamme pas tous de suite, c'est très rare).
                                  Sinon, une conception plus poussée permettra de repondre de façon élégante à ce besoin, et je pense que l'ECS est plus indiqué.

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    15 janvier 2018 à 11:54:48

                                    michelbillaud a écrit:

                                    En fait il faut prendre le problème par l'autre bout :  tu es le développeur à qui on confie un boulot, qui est de réaliser une classe qui se conforme à une certaine interface de programmation.

                                    Tout à fait d'accord, mais, une fois que le boulot est fini sur une fonction ou sur une classe, ce qu'il te reste à faire c'est... l'utiliser

                                    Ne serait-ce que parce qu'une fonction ou une classe qui n'est pas utilisée est... inutile, et se transforme en perte de temps.

                                    Mais, quand tu décides de créer une instance de ta classe, quand tu décides d'appeler une fonction, tu n'en est plus le développeur, même si tu viens d'y mettre le ; ou l'accolade final(e): tu en es ... un utilisateur "comme les autres"

                                    Et, partant de là, même si tu en connais tous les rouages, toute la mécanique interne, tu n'as pas à faire jouer cette connaissance pour décider de l'utilisation que tu fais de ta fonction / de ta classe: tu dois faire "comme si" tu n'en savais... que ce que l'on a bien voulu t'en dire.

                                    De toutes manières, les choses sont telles que tu oublieras quand même très rapidement les rouages internes d'une fonction ou d'une classe que tu as développé, et que tu te retrouveras donc quand même dans cette situation d'utilisateur ayant une connaissance "superficielle" de la classe ou de la fonction.

                                    Par exemple du code pour piloter un véhicule, avec des fonctions avancer(distance) et tourner(angle).  Et ce seront exactement les mêmes fonctions pour piloter une bagnole à un moteur ou un tank qui en a deux.  Ou a un aeroglisseur qui a des ventilateurs. Donc la variable "moteur", tu te la gardes pour toi, elle est liée à une réalisation particulière de la notion abstraite de véhicule qui avance et qui tourne.

                                    Normalement, l'interface elle sera décrite par une classe abstraite (en C++) ou mieux, une interface en Java. Comme ça le code qui commande sera polymorphe, complètement indépendant de la façon dont les véhicules font marcher leurs moteurs.

                                    Mais c'est donc bien la preuve que, à partir du moment où tu instancie une classe, où tu fais appel à une fonction, la seule connaissance que tu doive faire valoir est celle... de son interface publique, de sa signature ou de la documentation utilisateur qui est fournie avec, et donc que tu dois agir comme l'utilisateur de cette classe / de cette fonction et non en tant que développeur de celle-ci.

                                    Et donc, c'est bien la preuve que, les données qui sont utilisées par une classe ou par une fonction, les algorithmes mis en oeuvre, tu ne dois t'en inquiéter que... le temps d'écrire (ou de corriger) le code de la classe ou de la fonction.  Une fois que cette (courte) période de temps est finie, tu n'as plus à t'en inquiéter ;)

                                    -
                                    Edité par koala01 15 janvier 2018 à 11:57:12

                                    • 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
                                      15 janvier 2018 à 13:32:22

                                      c'est ça que j'dis, en fait.

                                      ce qui est privé, c'est parce qu'on peut en faire  *abstraction*

                                      Les plus vieux se souviendront des programmeurs finauds qui avaient l'idée stupide d'acheter les bouquins sur "les fonctions cachées de Windows", et les employaient dans leurs programmes.

                                      Résultat, à la mise à jour suivante, la "fonction cachée" avait disparu, ou ne faisait plus la même chose.  Si le monde avait été bien fait, elle aurait été privée, et ça ne serait pas arrivé : seules les fonctions officielles auraient été utilisées. Mais c'était du C, dont pas de "private". Déjà, le monde était mal fait. C'est Microsoft, faut dire.

                                      -
                                      Edité par michelbillaud 15 janvier 2018 à 13:37:51

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        15 janvier 2018 à 14:45:33

                                        Bon je comprends le principe, merci pour toutes vos précisions. Puis-je poster des bouts de code de mon projet ici ? Ca fera une espèce de blog pour partager ma progression. J'ai fini l'architecture en gros. Je ne veux pas vous faire travailler à ma place hein, je fais ça le soir pour le plaisir de toute façon :) Mais ceux qui le souhaitent pourront ainsi réagir.

                                        -
                                        Edité par Umbre37 15 janvier 2018 à 14:49:02

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          15 janvier 2018 à 15:53:53

                                          Je tiens à reformuler ma réponse précédente. Quand je parle de "setter" je parle de toute fonction par laquelle on DOIT passer pour modifier une donnée. Cela inclue donc des fonctions 'set()' (que je n'utilise pas souvent, personnellement) comme des fonctions du style 'blesser()' pour mon exemple. Il est évident que faire uniquement une fonction get() et une fonction set() sans réfléchir pour chaque donnée dans une classe est absurde.

                                          Mon explication essayait d'aller au delà des pattern spécifiques comme observer. La notion même de patterns complexes comme Observer ne sont pas des choses dont on devrait s'inquiéter quand on se demande si tout mettre en public est une bonne idée, je pense. Ce qu'il faut commencer par comprendre, comme tout le monde le dit ici, c'est qu'une classe est plus efficace à utiliser et sûrement dans beaucoup de cas plus simple à concevoir en tant que "Black box", fournisseur de services avec un fonctionnement interne que seul le développeur de cette même classe connait, du moins initialement. Il est une mauvaise idée de toujours se contenter d'utiliser les services prodigués par une classe sans se demander comment elle fonctionne à l'intérieur, car cela expose le développeur aux pièges liés au fait qu'une classe qu'il utilise n'a pas toujours été faite pour son problème précis ou même la catégorie de problèmes à laquelle ce dernier appartient, et est donc probablement moins efficace.

                                          necros211 a écrit:

                                          C'est du troll?

                                          Non, mais c'était une réponse ambigu, je l'admet. En revanche, peux-tu éviter ce genre de formules ultra condescendantes ?



                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            15 janvier 2018 à 16:44:13

                                            Quand on réfléchit correctement en termes de programmation orientée objet, une classe ne contient pas "une fonction pour modifier une variable de l'objet", mais "une fonction pour changer l'état de l'objet". A priori, on n'a pas à savoir si l'état est codé par une variable ou pas.

                                            Exemple un thermostat, qui aura deux fonctions

                                            class CapteurTemperature {
                                               float temperature;
                                               public : void aquisition() { temperature = ...... }
                                                        float degres_c() const { ... }
                                                        float degres_f() const { ... }
                                               ...
                                            }
                                            

                                            Tu n'as pas a savoir, de l'exterieur, comment est codée la température.

                                            Après, on peut utiliser les classes de C++ pour faire autre chose que de la programmation orientée-objet classique, propre etc. dans laquelle on met en oeuvre tous les moyens d'éviter de se prendre les pieds dans le tapis.

                                            On est en république, on a le droit de faire du code de goret, sans aucune abstraction, de tout mettre en global sans aucune protection. Du moment qu'on n'oblige personne à utiliser ou maintenir le résultat...


                                            > code d'une classe qui n'est pas faite pour le problème

                                            Ca c'est pour les blaireaux qui ne sont pas fichus de lire la documentation de la bibliothèque qu'ils emploient.  Et il y a une difference entre voir le code source, pour savoir comment çà a été programmé, et court-circuiter les protections. J'ai l'impression que tu confonds les deux.

                                            -
                                            Edité par michelbillaud 15 janvier 2018 à 16:49:05

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              15 janvier 2018 à 16:57:11

                                              Hoshiqua a écrit:

                                              Il est une mauvaise idée de toujours se contenter d'utiliser les services prodigués par une classe sans se demander comment elle fonctionne à l'intérieur, car cela expose le développeur aux pièges liés au fait qu'une classe qu'il utilise n'a pas toujours été faite pour son problème précis ou même la catégorie de problèmes à laquelle ce dernier appartient, et est donc probablement moins efficace.

                                              Si tu ne connais pas le fonctionnement interne du moteur de ton véhicule cela fait-il de toi un mauvais conducteur ? En tant que simple utilisateur on se contente d'utiliser les services à disposition pour la faire rouler, et c'est tout. Pas besoin de connaître l'intégralité du fonctionnement du bloc moteur pour utiliser un véhicule ;).

                                              Rassure-moi, quand tu utilises une lib, tu ne t'obliges pas à connaître la définition de chaque fonction de chaque classe que tu utilises quand même ? Si ?

                                              • Partager sur Facebook
                                              • Partager sur Twitter

                                              ...

                                                15 janvier 2018 à 17:22:26

                                                J'essaye de comprendre au moins le fonctionnement général, en fonction de l'humeur :p

                                                Ce dont j'ai peur c'est de me rendre compte qu'une classe / lib que j'utilise pour un problème particulier se révèle très inefficace longtemps après quand je cherche à optimiser les performances... A défaut de faire une analyse profonde de tout ce que j'utilise j'aime bien aller me renseigner au moins sur la complexité de certaines opérations.

                                                Ton analogie du véhicule est pas excellente (je trouve) parce qu'un véhicule peut difficilement être fait pour résoudre des problèmes plus généraux que se rendre d'un point A à un point B, donc l'étudier relève beaucoup plus de la connaissance technique applicable aux autres véhicules et à la réparation plutôt qu'à la vérification de l'efficacité de la solution au problème apportée.

                                                Néanmoins, good point quand même, faudrait pas faire croire aux gens qu'un dev connait par coeur tout ce qu'il utilise... t'imagines ce qu'il faudrait connaitre ? Jusqu'à la moindre ligne d'assembleur !

                                                -
                                                Edité par Hoshiqua 15 janvier 2018 à 17:24:50

                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  15 janvier 2018 à 17:30:24

                                                  je ne parle pas de vehicules, mais de logiciels de controle de véhicule.

                                                  Si tu avais lu ce qui précède, j'ai exliqué que les exemples stupides induisent les débutants en erreur, qui croient comprendre, par "intuition" et analogie. Visiblement tu es tombé dedans.

                                                  Les codes de controle de véhicule, ce n'est pas une analogie. C'est un exemple de _logiciel_. Un truc qui va contrôler des dizaines de microcontroleurs, capteurs et actionneurs un peu partout dans la bagnole.

                                                  Le code de contrôle, il n'a pas de roues et on ne le fait pas réparer par le garagiste.  Et la voiture qu'on met à réparer, c'est un tas de ferraille, pas un composant logiciel.

                                                  Un exemple concret ? 5 millions de lignes de code pour le Rover envoyé sur Mars, 150 millions de lignes de code pour une bagnole de chez Ford.

                                                  https://www.eitdigital.eu/news-events/blog/article/guess-what-requires-150-million-lines-of-code/

                                                  -
                                                  Edité par michelbillaud 15 janvier 2018 à 17:35:28

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    15 janvier 2018 à 18:26:06

                                                    A propos des véhicules, je répondais à Guit0Xx. Excuse moi pour mon manque de précision.

                                                    Et oui, je suis sûr qu'un logiciel de contrôle de véhicule est quelque chose de très complexe. Je ne m'y suis jamais intéressé donc je ne saurais en parler plus en détail.

                                                    Pourquoi constamment rentrer dans la comparaison ? N'est-il pas possible d'avoir une discussion sans tenter de prendre l'autre de haut parce qu'il serait un "débutant qui croit comprendre" ? Si tu étais vraiment face à un débutant, crois-tu qu'il serait malin de lui envoyer ça dans la poire ?

                                                    J'arrête là avant de lancer une dispute. Je pense que le sujet est clos, l'OP a eu beaucoup plus que ce qu'il lui fallait en réponses, avec tous les points de vue qu'on puisse imaginer :p

                                                    -
                                                    Edité par Hoshiqua 15 janvier 2018 à 18:26:52

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      15 janvier 2018 à 18:28:00

                                                      Hoshiqua a écrit:

                                                      Si tu étais vraiment face à un débutant, crois-tu qu'il serait malin de lui envoyer ça dans la poire ?

                                                      Vu que Michel les a aussi en cours, je pense qu'il peut confirmer que c'est tout à fait possible :p .

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter

                                                      Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

                                                        15 janvier 2018 à 23:09:57

                                                        Ksass`Peuk a écrit:

                                                        Hoshiqua a écrit:

                                                        Si tu étais vraiment face à un débutant, crois-tu qu'il serait malin de lui envoyer ça dans la poire ?

                                                        Vu que Michel les a aussi en cours, je pense qu'il peut confirmer que c'est tout à fait possible :p .


                                                        Oh, ça, sur les "misconceptions", y a pire... et ce que j'en disais, c'est qu'elles ne sont pas "de la faute au débutant", il n'est donc pas utile de faire le numéro de la victime outragée. C'est les choix d'exemples par les enseignants (professionnels ou autoproclamés auteurs de cours) qui sont mauvais, et depuis des décennies (l'histoire des cercles qui sont des ellipses, c'est caractérisque du problème très mal posé).

                                                        Le débutant, quand on lui fait voir un bout du monde réel et que c'est en pleine contradiction avec ses idées sorties d'on ne sait où, ça le secoue un peu, forcément. Et aussi quand lui explique pourquoi les exemples qu'il trouve partout sont ridicules, et trompeurs.

                                                        A part ça il n'a toujours pas compris que les (logiciels de) voiture, ce n'était pas une comparaison ni une analogie, mais un exemple de logiciel, qui a tout intérêt à être développé de façon modulaire.

                                                        -
                                                        Edité par michelbillaud 15 janvier 2018 à 23:19:53

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          16 janvier 2018 à 0:41:24

                                                          Ca marche aussi avec le fonctionnement des karts ? Parce que j'adore Mario Kart et j'aimerais bien connaitre comment ca fonctionne, un kart.
                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            16 janvier 2018 à 7:15:44

                                                            Mets pas trop d'essence dans la console, ca va deborder
                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                              16 janvier 2018 à 10:26:00

                                                              Sinon petite question légèrement hors sujet, mais il n'y aurais pas des Katas accessible aux debutants avec test et autre pour expliquer en pratique une bonne conception objet?

                                                              Car c'est vrais que sur internet, il y a beaucoup de mauvais exemple, et quand j'explique l'objet j'ai du mal a trouver les bon exemples, accessible.

                                                              Apres peu importe le langage tant que les exemples sont bon :D

                                                              • Partager sur Facebook
                                                              • Partager sur Twitter

                                                              Architecte logiciel - Software craftsmanship convaincu.

                                                              Accesseurs, quelle utilité ?

                                                              × 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