Partage
  • Partager sur Facebook
  • Partager sur Twitter

Accesseurs, quelle utilité ?

    16 janvier 2018 à 10:58:00

    C'est un peu galère, parce qu'il y a contradiction entre "petit exemple" et "truc pour lequel une approche objet est quasi-indispensable".  Si c'est pas quasi-indispensable, ça veut dire que le débutant fera comme il faisait avant, en se demandant à quoi sert vraiment ce qu'on lui a raconté.

    - typiquement, quand on veut causer de récursion, c'est une très mauvaise idée de prendre comme exemple la factorielle. Le débutant sait déjà faire autrement, et de façon plus simple, et plus efficace.  Par contre, si on parle de visiter tout un répertoire, avec fichiers et sous répertoires, là c'est la solution simple et naturelle que tout le monde comprend du premier coup.

    - on a un exemple ici : quelqu'un qui n'est pas convaincu que les membres privés servaient à quelque chose. C'est parce que les exemples qu'il a vu sont des "POD" (plain old data) où les structures feraient parfaitement l'affaire.

    Cette année pour enseigner la POO j'ai pris une approche différente. On part d'une situation où on a un jeu où il faut deviner des nombres (plus ou moins) et qui a une interface graphique (fournie) avec un champ de saisie, un label pour afficher le message, et un bouton.

    Dès le départ, on part d'un découpage en 2 objets : un qui s'occupe des composants graphiques, l'autre de la partie.

    Le truc qui gére la partie de plus ou moins, il est complètement indépendant du graphisme.  Il y a une fonction pour lui recevoir une proposition, et une autre pour savoir le message qui en résulte (trop petit, trop grand, ...). Eventuellement une autre pour savoir si c'est fini. Interface clairement définie, partage strict des responsabilités. et tout ce qui ne fait pas partie de l'interface est public. Ca devrait régler la question de l'abstraction.

    A partir de là, on voit qu'on peut utiliser la même Vue pour beaucoup d'autres jeux, du moment qu'ils ont la même interface (classe abstr en C++, interface en java). Polymorphisme.  On peut aussi utiliser une vue différente (jeu en mode texte) avec les mêmes parties. Réutilisabilité.

    L'objectif c'est d'avoir en premiers exemples des situations où le partage de responsabilité est déjà fait, est logique et a un intérêt absolument évident. Et qu'ils aient une impulsion pour le reproduire ensuite dans d'autres d'exemples. Parce que si on attend qu'ils l'infèrent à partir de la définition d'une classe = données + fonctions sur les Oiseaux et les Mammifères qui sont des Animaux, ça marche pas.

    -
    Edité par michelbillaud 16 janvier 2018 à 13:58:30

    • Partager sur Facebook
    • Partager sur Twitter
      16 janvier 2018 à 11:27:06

      necros211 a écrit:

      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



      Raisonne en therme de services / fonctionalités / comportement plutot qu'en therme de données sera déja un bon départ.
      • Partager sur Facebook
      • Partager sur Twitter
        16 janvier 2018 à 19:12:04

        Michel, c'est intéressant ce que tu dis.

        Dans les formations que je donne (5j pour couvrir bcp de choses), j'ai justement le problème de devoir trouver des exemples OO qui sont pertinents. Là, je tends à partir vers l'exo de l'aquarium, sans dégainer pour autant les ECS. Avec le design pattern strategy, on a déjà un bon début.

        • 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.
          16 janvier 2018 à 23:20:22

          Sans vouloir interrompre vos échanges sur la pédagogie c++ :) j'ai essayé d'avancer sur un ECS. Je pense avoir fini dans les grandes lignes. Puis-je poster mon code pour avoir des retours sur l'architecture ?
          • Partager sur Facebook
          • Partager sur Twitter
            16 janvier 2018 à 23:56:56

            Umbre37 a écrit:

            Sans vouloir interrompre vos échanges sur la pédagogie c++ :) j'ai essayé d'avancer sur un ECS. Je pense avoir fini dans les grandes lignes. Puis-je poster mon code pour avoir des retours sur l'architecture ?

            Oui, bien sur...

            Par facilité (en plus, ce pourrait t'être utile pour la suite), place le peut-être sur un site de gestion de version concurrente, comme github, surtout s'il est composé de plusieurs fichiers... (et donnes nous en l'adresse)

            Ce sera surement plus facile que de copier l'ensemble des fichiers que tu as créé, et te permettra en outre d'avoir une historique des changements, utiles lorsque l'on introduit une régression par erreur ;)

            • 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
              17 janvier 2018 à 0:38:28

              Navré je ne maitrise pas encore github. Là l'avantage c'est que tout est sur une page, même si c'est un peu long cela permet de voir comment les choses sont imbriquées. Si le projet se développe je ferai ca promis :) J'ai bien conscience que le code que je poste est très loin de vos exigences en terme de programmation. Cependant comme je souhaite progresser j'accueillerai avec plaisir toute critique constructive. Pour le moment les .h et les .cpp ne sont pas séparés mais c'est prévu. Le code compile comme il est. Je l'ai même utilisé avec la sfml pour gérer des tonnes de sprites qui disparaissent et apparraissent aléatoirement. Ca fonctionne. Ici j'ai fait un main tout petit pour montrer le principe.

              #include <iostream>
              #include <string>
              #include <vector>
              #include <map>
              #include <set>
              
              /*-------------------------------------------------------------------------------
              Classe ID */
              
              class base;
              class ID
              {
              private:
                  size_t m_ID; // le type de l'ID sera éventuellement modifié plus tard (éventuellement plus petit ?)
                  static std::vector<size_t> IDs_libres; // réserve pour pouvoir réutiliser les ID des entités supprimées
                  static size_t cpt; // compteur pour pouvoir attribuer un nouvel ID à chaque nouvelle entité
                  friend bool operator<(ID const& a, ID const& b);
                  friend bool operator==(ID const& a, ID const& b);
                  friend std::hash<ID>;
                  friend class base;
                  void sup() //fonction à appeler pour ajouter l'ID de l'entité détruite à la réserve
                  {
                      IDs_libres.push_back(m_ID); 
                  }
              public:
                  ID()
                  {
                      if(IDs_libres.empty())  // on vérifie si la réserve d'IDs précedemment supprimés est vide
                      {
                          m_ID = cpt++; // si oui le nouvel ID sera égal au compteur + 1, le compteur est incrémenté
                      }
                      else
                      {
                          m_ID = IDs_libres.back(); //sinon j'utilise comme nouvel ID le dernier à avoir été mis en réserve
                          IDs_libres.pop_back(); // je l'enlève de la réserve
                      }
                  }
              };
              std::vector<size_t> ID::IDs_libres;
              size_t ID::cpt = 0;
              namespace std
              {
                  template<>
                  class hash<ID>
                  {
                      public:
                      size_t operator()(ID const& id) const 
                      {
                          return id.m_ID;
                      }
                  };
              };
              bool operator<(ID const& a, ID const& b)
              {
                  return (a.m_ID < b.m_ID);
              }
              bool operator==(ID const& a, ID const& b)
              {
                  return (a.m_ID == b.m_ID);
              }
              
              /*-------------------------------------------------------------------------------
              Classe qui accumule toutes les instances de data et cara. Permet de supprimer toutes les datas d'une entité grâce à une seule fonction
              */
              
              class base
              {
              private:
                  static std::vector<base *> table; // variable statique contenant des pointeurs sur toutes les instances filles.
              public:
                  base()
                  {
                      table.push_back(this);
                  }
                  virtual void erase(ID const& a) =0;
                  static void clear(ID a) // fonction pour supprimer une entité et toutes ses données
                  {
                      for(auto i : table)
                      {
                          i->erase(a);
                      }
                      a.sup();
                  }
              };
              std::vector<base *> base::table;
              
              /*-------------------------------------------------------------------------------
              Classe 'data' qui gère les données sous forme de map d'ID.
              */
              
              template<typename T> 
              class data : base
              {
              private:
                  std::map<ID, T> table; // contient les données
                  typename std::map<ID, T>::iterator it1;
                  typename std::map<ID, T>::iterator it2;
                  bool verrou;
                  
              public:
                  T& operator[](ID const& a)
                  {
                      return table[a]; 
                  }
                  template<typename T2>
                  void operator()(T2 const& foo) // permet de passer une fonction sur toutes les entités possèdant cette data
                  {
                      verrou = false;    // grace au verrou, la fonction ne plantera pas si foo() détruit l'entité en cours de traitement
                      for (it1 = table.begin(); it1 != table.end();)
                      {
                          foo(it1->first);
                          if(!verrou) ++it1;
                          else verrou = false;
                      }
                  }
                  void erase(ID const& a)
                  {
                      it2 = table.find(a);
                      if (it2 != table.end())
                      {
                          verrou=true;
                          it1 = table.erase(it2); // si erase() est appellée, it1 est incrémenté pour ne pas faire planter la surcharge d'opérateur ()
                      }
                  }
                  bool exist(ID const& a)
                  {
                      it2 = table.find(a);
                      return (it2 != table.end());
                  }
              };
              /*-------------------------------------------------------------------------------
              Même chose que data mais pour une simple caractéristique : l'entité possède une propriété,
              ce qui est exprimé par la présence de son ID dans le set, sans que cela corresponde à une  donnée)
              */
              class cara : base 
              {
              private:
                  std::set<ID> table;
                  std::set<ID>::iterator it1;
                  std::set<ID>::iterator it2;
                  bool verrou;
              public:
                  void insert(ID const& a)
                  {
                      table.insert(a); 
                  }
                  template<typename T>
                  void operator()(T const& foo)
                  {
                      verrou = false;
                      for (it1 = table.begin(); it1 != table.end();)
                      {
                          foo(*it1);
                          if(!verrou) ++it1;
                          else verrou = false;
                      }
                  }
                  void erase(ID const& a)
                  {
                      it2 = table.find(a);
                      if (it2 != table.end())
                      {
                      verrou=true;
                      it1 = table.erase(it2);
                      }
                  }
                  bool exist(ID const& a)
                  {
                      it2 = table.find(a);
                      return (it2 != table.end());
                  }
              };
              
              cara magiciens;
              cara rodeurs;
              cara guerriers;
              
              data<unsigned int> vies;
              data<std::string> noms;
              
              void presentation(ID const& id)
              {
                  std::cout << "Je m'appelle "<< noms[id] <<", j'ai "<< vies[id] << " vies."<< std::endl;
              }
              
              int main()
              {
                  ID a;
                  noms[a] = "Gandalf";
                  magiciens.insert(a);
                  vies[a] = 100;
              
                  ID b;
                  noms[b] = "Aragorn";
                  rodeurs.insert(b);
                  guerriers.insert(b);
                  vies[b] = 50;
              
                  noms(presentation);
                  /* Affiche :
                  Je m'appelle Gandalf, j'ai 100 vies.
                  Je m'appelle Aragorn, j'ai 50 vies.
                  */
              
                  guerriers(base::clear); // élimine tous les guerriers et leurs données
              
                  noms(presentation);
                  /* Affiche :
                  Je m'appelle Gandalf, j'ai 100 vies.
                  */
                  return 0;
              }

              Je trouve que c'est assez souple et lisible dans le main. Ce qui ne me plait pas, pour en revenir au sujet de ce topic, c'est que que mes cara et data soient dans l'espace global. Mais cela me permet de définir facilement des fonctions qui les utilisent. Qu'en pensez-vous ?

              -
              Edité par Umbre37 17 janvier 2018 à 2:03:23

              • Partager sur Facebook
              • Partager sur Twitter
                17 janvier 2018 à 3:34:31

                Umbre37 a écrit:

                Navré je ne maitrise pas encore github.

                Oh, il n'y a pas de mal... Ce que j'en dis, tu sais, c'est juste un conseil qui pourrait s'avérer utile par la suite...

                Maintenant, je me rend bien compte que, entre l'apprentissage du C++ (qui est un langage complexe) et tout le reste, tu te trouves obligé de faire certains choix ;)

                Mais, bon, comme nous sommes quand même ici pour essayer de te donner de bons conseils: essaye de mettre la connaissance d'un système de gestion de versions concurrentes "assez haut" dans ta todo-list :D

                Là l'avantage c'est que tout est sur une page, même si c'est un peu long cela permet de voir comment les choses sont imbriquées. Si le projet se développe je ferai ca promis :) J'ai bien conscience que le code que je poste est très loin de vos exigences en terme de programmation.

                Non, ca va encore en termes de programmation...  Il y a des choses à dire en terme de conception, mais le code est compréhensible, correctement indenté, et les fonctions sont courtes.

                On regrettera surtout la présence de variables globales, quelques identifiants tronqués et quelques commentaires qui n'ont pas grand chose à faire dans le code, mais pour le reste, on a déjà vu pire :D

                Cependant comme je souhaite progresser j'accueillerai avec plaisir toute critique constructive.

                Ca tombe bien, parce que je n'avais pas l'intention de te laisser t'en tirer à si bon compte :D. 

                Pour le moment les .h et les .cpp ne sont pas séparés mais c'est prévu.

                Fais le rapidement, alors, car c'est sans doute la pratique la plus importante du C++, et qu'elle nécessite un peu d'habitude ;)

                Et plus tu utiliseras cette pratique, plus tu t'habitueras à l'utiliser, et tu plus tu l'utiliseras correctement... Mais c'est comme tout, finalement, hein? :D

                Le code compile comme il est. Je l'ai même utilisé avec la sfml pour gérer des tonnes de sprites qui disparaissent et apparraissent aléatoirement. Ca fonctionne. Ici j'ai fait un main tout petit pour montrer le principe.

                Ce qui est déjà pas si mal, mais bon, comme je te l'ai dit, je ne vais pas te laisser t'en tirer à si bon compte.  Alors, accroches toi à tes bretelles... :

                Ta classe ID en fait trop: La notion d'Id est juste là pour te permettre d'identifier l'entité à laquelle appartient un composant. Ce n'est donc pas à elle de garder une trace des id qui sont disponibles!  Ca, c'est du recours "d'autre chose" (un "IdHolder"?) si on veut respecter le SRP.

                Si bien que ta notion d'ID pourrait se réduire à ... un simple alias sur un type numérique entier, comme

                using ID = size_t;

                Et, l'un services dont tu auras besoin est, bien sur, de permettre de créer une nouvelle entité, ce qui se traduirait sans doute par une fonction proche de

                ID createEntity(){
                    /* nous ferions sans doute usage de la classe IdHolder pour
                     * obtenir l'ID de l'entité créée 
                     */
                }

                Ensuite, il faut garder en tête que tous les composants que tu vas créer se rapporte systématiquement à une entité particulière.  Et que les composants ont, systématiquement sémantique de valeur.  Pas de bol, tu es tombé dans un des pièges du concept de ECS: tu as créé une hiérarchie de classes pour représenter tes composants.

                Si héritage il devait y avoir, ce serait un héritage basé sur le CRTP, quelque chose proche de

                template <typename Data>
                struct Component{
                    /* tous les composants sont rattachés à une entité
                     * particulière, nous devons donc fournir l'id de
                     * cette entité
                     */
                    Component(ID id):entityId{id}{}
                    ID entityId;
                }
                struct Live : public Component{
                    Live(ID id,size_t max, size_t actual = max):
                        Component{id}, max{max}, actual{actual}{
                    }
                    size_t max;
                    size_t actual;
                };
                struct Mana : public Component{
                    Mana(ID id,size_t max, size_t actual = max):
                        Component{id}, max{max}, actual{actual}{
                    }
                    size_t max;
                    size_t actual;
                };

                L'idée générale est sans doute de regrouper les composants identiques entre eux, et de trouver le compromis idéal entre la possibilité de chercher rapidement un composant sur base de l'id de l'entité à laquelle il est rattaché et un parcours de l'ensemble des composants qui évitera le plus les cachemiss.

                Mais nous avons donc besoin d'une "collection" pour chaque composant existant. Cela pourrait prendre une forme proche de

                /* la recherche d'un composant sur base de l'id de l'entité
                 * auquel il est rattaché est loin d'être optimale, 
                 * mais ce genre de collection évitera les cachemiss lors d'un
                 * parcours séquentiel 
                 */
                template <typename T>
                class ComponentHolder{
                public:
                    static_asert(std::is_base_of(T, Component<T>)::value,
                    "Components should have a Component<T> base");
                    using iterator = typename std::vector<T>::iterator;
                    using const_iterator = typename std::vector<T>::const_iterator;
                    /* on veut pouvoir rajouter un élément */
                    template <typename ... Args>
                    void add(ID id, Args ... args){
                        items_.emplace_back(id, args ...);
                    }
                    /* on veut pouvoir utiliser cette classe comme n'importe
                     * quelle autre collection.  Pour cela, nous avons
                     * besoin des fonctions begin() et end()
                     */
                    iterator begin(){
                        return items_.begin();
                    }
                    iterator end(){
                        return items_.end();
                    }
                    const_iterator begin() const{
                        return items_.begin();
                    }
                    const_iterator end() const{
                        return items_.end();
                    }
                    /* on veut pouvoir retirer l'élément qui est relié
                     * à une entité particulière
                     */
                    void remove(ID id){
                        auto it = find(id);
                        if(it!= items_.end())
                            items_.erase(it);
                    }
                    iterator find(ID id){
                        return std::find(items_.begin(), items_.end(),
                        [&](auto const & it){return it.entityId == id;});
                    }
                    /* on veut savoir si l'élément existe pour une entité
                     * particulière
                     */
                    bool exits(ID id) const{
                        return const_cast<ComponentHolder<T> &>(*this)
                           .find(id)!= end();
                    }
                private:
                    std::vector<T> items_;
                }

                Idéalement, la création d'une entité serait suivie de la création de tous les composants qui y sont rattachés, ce qui se traduirait par quelque chose comme

                auto id = createEntity(); 
                addComponent<Live>(id,120);
                addComponent<Level>(id,18);
                addComponent<StrongLevel>(id,15);
                addComponent<MagicLevel>(id,22);
                addComponent<Name>(id, "Grock");
                addComponent<Race>(id, ork);
                addComponent<Caste>(id, chaman);
                addComponent<Description>(id, "Le plus méchant chaman ork que tu va croiser");
                auto stickId = createEntity();
                addComponent<Stick>(stickId, 14);
                addComponent<Magic>(stickId, 18);
                addComponent<Damage>(stickId, 80);
                addComponent<FreeseDamage>(stickId, 150);
                addComponent<MinimumLevel>(stickId, 17);
                addComponent<Description>(stickId, "Le baton de Grock");
                addComponent<Owner>(stickId, id);

                Comme tu peux le voir, les neuf premières lignes m'ont permis de définir les caractéristiques d'un chaman ork nommé Grock, les sept suivantes m'ont permis de créer les caractéristique d'un baton magique, et la dernière ligne a défini Grock comme étant le propriétaire du bâton.

                Mais là, je vais déjà très loin... Tu pourrais tout aussi bien avoir 15 fonctions différentes qui auraient toutes comme finalité de créer un composant particulier qui serait rattaché à une entité particulière ;)

                Le seul truc, c'est que l'on se rend compte que la notion de ComponentHolder dont je te parlais plus haut devra être accessible à pas mal d'endroits, car, après, il y aura tous les services qui voudront accéder aux différents composants, pour une raison ou une autre ;)

                Et, bien sur, je ne te parles pas de tout ce qui te facilitera la "gestion" des entités au quotidien, comme le fait de rajouter une super épé magique de la mort qui tue lorsque le joueur aura réussi une quête :D

                -
                Edité par koala01 17 janvier 2018 à 3:35:24

                • 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
                  17 janvier 2018 à 11:19:41

                  Merci beaucoup de ta réponse. Je n'emploie pas les termes appropriés pour nommer mes classes, il faut que je change ca. Mais je trouve que nos solutions sont identiques ou presque. Ta classe ComponentHolder ressemble beaucoup à ma classe data (dans le sens où elle a le même rôle). Après toi tu utilises un vector (c'est certainement mieux), et moi une map(c'est peut-être plus lent).

                  koala01 a écrit:

                  Le seul truc, c'est que l'on se rend compte que la notion de ComponentHolder dont je te parlais plus haut devra être accessible à pas mal d'endroits, car, après, il y aura tous les services qui voudront accéder aux différents composants, pour une raison ou une autre ;)


                  C'est bien pour cela que mes ComponentHolder (mes data et cara) sont dans l'espace global. Sinon je ne peux pas écrire ma fonction "se présenter". Pour être clair une cara c'est par exemple, être soumis à la gravité, être visible, bref c'est jute la liste des entités qui partagent une propriété (sans que cela corresponde à une data), ce n'est donc pas une hierarchie de component... Avoir une position implique des données (x, y, z). Etre affichable implique des données (une référence sur une texture ou que sais-je). Etre hors de l'écran ou bien être un mage ou bien être soumis à la gravité n'implique pas de donnée. C'est la seule raison pour laquelle il existe deux classes.

                  Data -> pour stocker les components classiques qui impliquent une donnée

                  Cara -> pour stocker des propriétés qui n'impliquent pas de données

                  Sinon je trouve vraiment que nos codes sont similaires sur le principe. J'ai juste géré avec la classe Base (mère de Data et Cara qui garde un pointeur sur toutes les instances de ses filles) la suppression de tous les components d'une entité (chose pas évidente si ils sont dans des ComponentHoler différents, sans que l'on sache à l'avance lesquels et combien il en a). 

                  -
                  Edité par Umbre37 17 janvier 2018 à 12:44:42

                  • Partager sur Facebook
                  • Partager sur Twitter
                    17 janvier 2018 à 12:54:19

                    Umbre37 a écrit:

                    Merci beaucoup de ta réponse. Je n'emploie pas les termes appropriés pour nommer mes classes, il faut que je change ca. Mais je trouve que nos solutions sont identiques ou presque. Ta classe ComponentHolder ressemble beaucoup à ma classe data (dans le sens où elle a le même rôle). Après toi tu utilises un vector (c'est certainement mieux), et moi une map(c'est peut-être plus lent).

                    Le problème, c'est que toi, tu connais l'objectif poursuivi par la classe data, parce que tu viens d'en écrire le code...

                    Mais, pense à nous, pauvres malheureux, qui n'étions pas penchés sur ton épaule lorsque tu as écrit ton code, et qui n'avons que le code à notre disposition pour nous faire une idée de ce que tu as voulu faire.

                    Et si cela ne te suffit pas, penses à toi, dans un mois ou dans trois, quand tu auras écrit plusieurs centaines (milliers??) de lignes de code supplémentaires, et que tu auras donc eu tout le temps d'oublier ce à quoi tu a pensé en écrivant ton code.

                    Tu seras exactement dans la même situation que nous: les seules informations dont tu disposera seront... fournies par le code

                    C'est la raison pour laquelle il est primordial que le code exprime aussi clairement que possible ce que tu as voulu faire.

                    Je te faisais la remarque que certains commentaires étaient parfaitement inutiles.

                    Je comprend que, en tant que débutant, tu te sente rassuré d'avoir des commentaires, mais, à quelques exceptions près (*), le meilleur commentaire est celui... que tu ne devras pas écrire.

                    Si bien que, de manière générale, si tu envisages de mettre un commentaire pour expliquer à quoi sert une variable, un type de donnée ou une fonction, tu devrais envisager de choisir un autre nom pour cet élément, de manière à ce que le (nouveau) nom indique clairement l'objectif poursuivi par cet élément.

                    En nommant ma classe ComponentHolder, je définis clairement quel en est l'objectif: c'est un porteur ( c'est la traduction de Holder) de... composants (traduction de Component).

                    J n'ai donc pas besoin de commentaires pour préciser ses objectifs, vu que son nom les indique très clairement ;)

                    C'est bien pour cela que mes ComponentHolder (mes data et cara) sont dans l'espace global.

                    Je le sais bien, mais c'est la pire des solutions que tu pouvais envisager...

                    A choisir, un patron de conception Singleton ou (mieux encore) un patron de conception ServiceLocator auraient sans doute été préférables ;)

                    Et, sinon, il y a toujours la solution "la plus simple" qui consiste à transmettre les données dont les fonctions ont besoin sous forme de paramètre (qui seront sans doute des références, éventuellement constantes ;) )

                    Sinon je ne peux pas écrire ma fonction "se présenter". Pour être clair une cara c'est par exemple, être soumis à la gravité, être visible, bref c'est jute la liste des entités qui partagent une propriété (sans que cela corresponde à une data), ce n'est donc pas une hierarchie de component... Avoir une position implique des données (x, y, z). Etre affichable implique des données (une référence sur une texture ou que sais-je). Etre hors de l'écran ou bien être un mage ou bien être soumis à la gravité n'implique pas de donnée. C'est la seule raison pour laquelle il existe deux classes.

                    Si, tu aurais pu le faire... Cela aurait juste nécessité un peu plus de réflexion... Or, c'est ce que l'on attend essentiellement d'un développeur: qu'il se "casse la tête" pour que les autres n'aient pas besoin de le faire après lui :D

                    • 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
                      17 janvier 2018 à 15:37:49

                      koala01 a écrit:


                      Je comprend que, en tant que débutant, tu te sente rassuré d'avoir des commentaires, mais, à quelques exceptions près (*), le meilleur commentaire est celui... que tu ne devras pas écrire.

                      Si bien que, de manière générale, si tu envisages de mettre un commentaire pour expliquer à quoi sert une variable, un type de donnée ou une fonction, tu devrais envisager de choisir un autre nom pour cet élément, de manière à ce que le (nouveau) nom indique clairement l'objectif poursuivi par cet élément.

                      Encore un problème de pédagogie, mais le mauvais usage des commentaires est inculqué par les exemples donnés dans les cours, comme

                      n ++;    // ajoute 1 à n

                      qui a été mis là pour expliquer ou rappeler une _syntaxe_  qu'un débutant n'a pas encore eu l'occasion de voir.

                      Mais une fois qu'on relit des programmes, ce genre de commentaires déclenche des envies de meurtres. Et avec plusieurs excellentes raisons

                      • si je lis un programme pour le modifier, c'est que je connais au moins les bases de la programmation. Sinon le mieux est que j'aille faire autre chose. Donc je sais forcément ce que n++ fait. J'aime pas trop être pris pour un con, quand même.
                      • la présence d'un commentaire est en elle-même un "LISEZ-MOI", qui doit donc apporter de l'information supplémentaire par rapport au code. Alors je le lis. Je me demande quelle est la subtilité qui légitime sa présence. Je cherche bien, y en a pas. Merci de m'avoir fait perdre mon temps.
                      • au lieu de ça, il y avait peut être une raison non-évidente d'ajouter 1, et le commentaire serait "on ajoute 1 parce que ...". Mais c'est pas dit, ça explique rien.
                      • et probablement que la variable pourrait être mieux nommée.

                      http://wiki.c2.com/?CodeForTheMaintainer

                      <<

                      Always code as if the person who ends up maintaining your code is a violent psychopath who knows where you live.

                      I usually maintain my own code, so the as-if is true.

                      >>


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

                        Je ne les avaient pas mis pour moi les commentaires, juste pour le copier coller sur le forum. (je me dis que n'importe qui peut lire ca) :). Une question à propos des usages sur les noms de fonctions.

                        En quelles circonstances choisissez-vous tel ou tel nom entre remove, delete, clear, erase ?

                        EDIT : Bon j'ai passé pas mal de temps à résoudre le problème et essayer de comprendre (pour l'instant sans résultat). J'ai trouvé un article en anglais sur le service locator que j'ai pas bien compris. Je connais le singleton mais je ne sais pas trop comment l'appliquer ici. J'ai aussi essayé de compiler des bouts de codes de koala01 sans succès. Certaines choses m'échappent comme les variadic template que je ne maitrise pas bien. le std::is_base_of que je n'ai jamais vu ou encore les = dans les paramètres de fonction. J'ai pas mal de taf qui arrive et je programme sur mon temps libre donc je devrai remettre toutes ces choses passionnantes à plus tard : j'ai vraiment essayé de me creuser la tête pour garder les donnés encapsulées bien au chaud et pouvoir en même temps appliquer des fonctions dessus dans tous les sens... je n'y arrive pas :/ merci pour vos conseils en tout cas. Je reviendrai bientôt !

                        -
                        Edité par Umbre37 17 janvier 2018 à 17:59:15

                        • Partager sur Facebook
                        • Partager sur Twitter
                          19 janvier 2018 à 12:48:19

                          J'ai bien réfléchi, et plusieurs choses me dérangent. Premièrement les variables globales. Lorsqu'on écrit un programme on veille à déclarer nos variables de telle sorte qu'elles soient accessibles là où on en a besoin (pas plus et pas moins). Si on a besoin d'une variable partout, alors la logique veut qu'elle soit déclarée dans l'espace global. Existe-t-il de bonnes raisons de ne pas le faire ? Si oui lesquelles ?

                          Ensuite concernant cette histoire de penser service, et non pas données, je ne comprends pas. De toute façon programmer (dans n'importe quel paradigme), cela revient toujours à écrire des fonctions qui travaillent sur des données, pas de programme sans données sur lesquelles travailler, et pas de données sans programme qui les utilisent. En réalité on est obligé de penser aux deux.

                          Parant de là, y a-t-il une bonne raison de ne pas utiliser les classes en pensant prioritairement données ? Si vos classes ne font que fournir des services, alors autant écrire de simples fonctions avec des variables statiques, c'est la même chose. Car il n'y a aucune raison d'instancier plusieurs fois un unique service, alors que c'est l'intérêt d'une classe. Si ont peut créer de multiples instances d'une classe, c'est bien parce que ce qui caractérise une classe, c'est d'abord un certain genre de donnée et non pas un un certain genre de service.

                          Exemple de service : nettoyer_la_table();

                          Exemple de donnée : table table_a_manger, table_du_salon, table_du_garage, table_de_la_chambre ;

                          Pouvoir instancier table, je vois l'intérêt (il y en a plusieurs du même type), mais le service de nettoyage est le même pour toutes les tables, alors pourquoi l'instancier ?

                          Par exemple, koala01 parlait d'une possible classe Force de RPG, qui aurait pour méthodes add(quantité),  remove(quantité), addMax(quantite), modifyMax(rapport). Ici on voit bien que ce qui caractérise cette classe (c'est à dire ce qui préside à la décision de sa création), c'est le type de donnée (la force), et non pas le service. La preuve est simple : la classe contient UN attribut, et 4 méthodes (qui toutes sont des CONSÉQUENCES du type de donnée - et non pas l'inverse). Y a-t-il une chose que j'ai mal comprise ? Parce que pour moi, penser service, ca revient à considérer les classes comme des fonctions compliquées, donc : à quoi ca sert puisque je sais déjà écrire des fonctions, qui elles, ont l'énorme avantage de ne pas devoir être instanciées ? Merci encore de votre aide.

                          PS : J'ai essayé de compiler des extraits du code de Koala01 pour l'ECS, pour le moment sans succès mais je ne renonce pas :)

                          -
                          Edité par Umbre37 19 janvier 2018 à 13:16:24

                          • Partager sur Facebook
                          • Partager sur Twitter
                            19 janvier 2018 à 13:31:46

                            Umbre37 a écrit:

                            Premièrement les variables globales. Lorsqu'on écrit un programme on veille à déclarer nos variables de telle sorte qu'elles soient accessibles là où on en a besoin. Si on a besoin d'une variable partout, alors la logique veut qu'elle soit déclarée dans l'espace global. Existe-t-il de bonnes raisons de ne pas le faire ? Si oui lesquelles ?

                            Si c'est une constante, ça n'a pas une importance cruciale, même si c'est discutable. Mais le fait est que comme qui dirait une variable globale n'est réellement globale si "on a besoin d'elle partout", et ça, force est de constater que ce n'est pour ainsi JAMAIS le cas. Je te conseille de lire ceci : ça parle de "singleton", une globale en smoking.

                            Les variables globales introduise un couplage monstrueux dans le code c'est une dépendance qui va rendre le suivi de la logique associé à la variable très difficile et donc la maintenance du code associé complexe.

                            Umbre37 a écrit:

                            Ensuite concernant cette histoire de penser service, et non pas données, je ne comprends pas. De toute façon programmer, cela revient toujours à écrire des fonctions qui travaillent sur des données, pas de programme sans données sur lesquelles travailler, et pas de données sans programme qui les utilisent. En réalité on est obligé de penser aux deux.

                            Bien sûr qu'on est obligé de penser aux deux. Mais le fait est ce que la très vaste majorité de ce que tu écris, c'est des traitements. Il y a beaucoup plus de lignes de codes qui sont là pour expliquer comment traiter les données que de lignes de code pour déclarer les données. Pour caricaturer : si tu n'as pas de ligne de code associée à une donnée : la donnée ne sert à rien elle ne devrait pas être là, et généralement, on a largement plus d'une ligne de code pour une donnée particulière.

                            A partir de là, l'idée est très simple : la majorité de mon boulot consiste à définir comment remplir une tâche complexe qui va, certes, nécessiter des données, mais qui va surtout nécessiter de bien définir les traitements. La majorité du boulot c'est "produire un traitement" donc c'est sur ce traitement que je dois me concentrer en priorité, c'est lui le plus important. Les données sont secondaires et seront choisies en fonction du traitement à réaliser.

                            Umbre37 a écrit:

                            Parant de là, y a-t-il une bonne raison de ne pas utiliser les classes en pensant prioritairement données ?

                            Ben tu peux t'essayer à l'exercice : tu auras de jolis paquets de données bien définis et ... rien pour les relier. Encore une fois, programmer c'est définir un traitement.

                            Umbre37 a écrit:

                            Si vos classes ne font que fournir des services, alors autant écrire de simples fonctions avec des variables statiques, c'est la même chose.

                            Penser aux services en premier ne veut pas dire "ne jamais penser aux données associées". Ça veut dire que la première chose à laquelle tu penses quand tu définis une classe c'est "quelle tâche je veux résoudre" et une fois que tu as définis les fonctionnalités qui correspondent, tu vas définir quelles sont les données qui sont nécessaires à la résolution de la tâche (et analyser s'il est mieux de les recevoir en entrée, ou de les stocker en interne, ou que sais-je). Cela va notamment te permettre de progressivement abstraire le raisonnement associé à ton programme.

                            Exemple typique, la notion de fichier : les services associés aux classes qui permettent leur manipulation ne disent rien du contenu des objets qui correspondent. Et même si on regarde l'implémentation, on voit juste que l'on fait des appels systèmes qui une nouvelle fois ... ne nous disent rien des données associées à ces appels en interne. Justement parce que l'on veut ne pas avoir à s'occuper de ces détails qui n'apporte rien sémantiquement à ce que l'on veut faire quand on dit "écrire machin dans le fichier truc".

                            Umbre37 a écrit:

                            Si ont peut créer de multiples instances d'une classe, c'est bien parce que ce qui caractérise une classe, c'est d'abord un certain genre de donnée et non pas un un certain genre de service.

                            Oui et là tu raisonnes au niveau de l'instance, pas au niveau des classes. Les objets sont différenciées par leur état (bien que l'orienté objet n'impose pas la notion d'état), mais là on parle de différencier les classes.

                            Umbre37 a écrit:

                            Par exemple, koala01 parlait d'une possible classe Force de RPG, qui aurait pour méthodes add(quantité),  remove(quantité), addMax(quantite), modifyMax(rapport). Ici on voit bien que ce qui caractérise cette classe (c'est à dire ce qui préside à la décision de sa création), c'est le type de donnée (la force), et non pas le service.

                            Je ne suis pas trop d'accord avec cette conception. En fait, on a plutôt envie d'une classe "Trait". La force étant alors juste l'instance d'un trait, que l'on a appelé force :

                            class Trait {
                            public:
                              Trait(int value, int max);
                              void add(int value);
                              void add_max(int value);
                              // ...
                            };
                            
                            class Character {
                              // ...
                            
                            private:
                              Trait force;
                              Trait defence;
                              Trait accuracy;
                              // ...
                            };

                            Umbre37 a écrit:

                            Parce que pour moi, penser service, ca revient à considérer les classes comme des fonctions compliquées, donc : à quoi ca sert puisque je sais déjà écrire des fonctions, qui elles, ont l'énorme avantage de ne pas devoir être instanciées ?

                            Une classe est avant tout le reste un ensemble de fonctions, si une classe n'a pas de fonction ... c'est juste une structure de données, pas une classe.

                            -
                            Edité par Ksass`Peuk 19 janvier 2018 à 13:32:14

                            • Partager sur Facebook
                            • Partager sur Twitter

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

                              19 janvier 2018 à 13:46:20

                              Bonjour, et merci de ta réponse. 

                              Ksass`Peuk a écrit:

                              Les variables globales introduise un couplage monstrueux dans le code c'est une dépendance qui va rendre le suivi de la logique associé à la variable très difficile et donc la maintenance du code associé complexe.


                              Qu'est-ce que le couplage? Ca fait plusieurs fois que je vois le terme sans comprendre, pour moi c'est un truc de méca :)
                              • Partager sur Facebook
                              • Partager sur Twitter
                                19 janvier 2018 à 14:07:07

                                Umbre37 a écrit:

                                En quelles circonstances choisissez-vous tel ou tel nom entre remove, delete, clear, erase ?

                                Delete, tu risques d'avoir du mal à l'utiliser (car c'est un mot clé), à part en mot composé (deleteSomeThing)

                                Pour les autres mot, c'est selon ce que tu veux faire: remove / erase retire un élément, clear supprime tous les éléments

                                le std::is_base_of que je n'ai jamais vu

                                C'est un type_trait qui permet de faire des vérifications à la compilation: std::is_base_of

                                ou encore les = dans les paramètres de fonction.

                                Ca permet de définir une valeur par défaut pour le paramètre (et donc d'éviter à l'utilisateur d'avoir à le donner si la valeur indiquée correspond à ses besoins )

                                J'ai bien réfléchi, et plusieurs choses me dérangent. Premièrement les variables globales. Lorsqu'on écrit un programme on veille à déclarer nos variables de telle sorte qu'elles soient accessibles là où on en a besoin. Si on a besoin d'une variable partout, alors la logique veut qu'elle soit déclarée dans l'espace global. Existe-t-il de bonnes raisons de ne pas le faire ? Si oui lesquelles ?

                                oui, il existe de bonnes raisons de ne pas le faire:

                                1. Par nature, une variable globale est... accessible depuis n'importe où, on est donc susceptible de la modifier depuis n'importe où, et ca, ca peut occasionner pas mal de problème
                                2. Plus une variable est accessible, plus on a tendance à l'utiliser, et souvent de manière idenique, et donc à copier du code (surtout si elle est accessible dans plusieurs unités de compilation), et donc à ne pas respecter le DRY
                                3. Plus une variable est accessible, plus on a tendance à l'utiliser, ce qui augmente la dépendance face à cette variable, et complique l'évolution de cette variable: on fini par avoir peur d'y toucher, de peur ... de "casser quelque chose"
                                4. très peu de variable doivent réellement être accessibles depuis "n'importe où"... Certaines devront être accessibles depuis "plusieurs endroits", mais ces endroits seront toujours clairement établis ;)
                                5. j'en passe, et de meilleures

                                Ensuite concernant cette histoire de penser service, et non pas données, je ne comprends pas. De toute façon programmer (dans n'importe quelle paradigme), cela revient toujours à écrire des fonctions qui travaillent sur des données, pas de programme sans données sur lesquelles travailler, et pas de données sans programme qui les utilisent. En réalité on est obligé de penser aux deux.

                                Tout à fait, mais tu dois d'abord penser au fonctions que tu veux créer, car ce sont les fonctions qui fournissent les comportements, et que ce sont les comportements qui t'intéressent dans un programme.

                                Les données, dans n'importe quel paradigme, ne sont là que pour... permettre aux fonctions de faire ce qu'elle doivent faire, pour permettre au programme de fournir les comportements que tu attends de sa part

                                Tu peux envisager de représenter les informations manipulées par tes différents comportements sous quantité de formes différentes, mais un comportement donné poursuivra, lui, toujours le même but.

                                Les comportements (les fonctions) seront donc beaucoup plus stable que la manière dont les données qu'ils utilisent sont représentées.

                                C'est pour cela qu'il est préférable de réfléchir d'abord en termes de comportement, même si, au final, tu finira bien par définir la manière dont les données sont représentées en mémoire ;)

                                Parant de là, y a-t-il une bonne raison de ne pas utiliser les classes en pensant prioritairement données ?

                                Non, il n'y a aucune raison de le faire, que ce soit pour des classes, ou pour des POD (Plain Old Data : les structures telles qu'elles existent en C, par exemple)

                                Regarde la structure FILE en C: l'utilisateur ne sait absolument pas de quoi elle est composée, et n'a aucun besoin de le savoir. Tout ce qu'il doit savoir, c'est qu'il peut utiliser les fonctions fopen, fread, fwrite et fclose pour la manipuler ;)

                                Si vos classes ne font que fournir des services, alors autant écrire de simples fonctions avec des variables statiques, c'est la même chose. Car il n'y a aucune raison d'instancier plusieurs fois un unique service, alors que c'est l'intérêt d'une classe.

                                Attention, j'ai bien dit que tu dois réfléchir à tes classes en tant que fournisseur de service

                                Mais, comme tu l'as si bien fait remarquer, le résultat d'une fonction dépend... des données qui lui ont été fournies. Et tu t'attends sans doute à ce que le résultat obtenu avec telle donnée soit différent de celui obtenu avec telle autre donnée du même type, mais présentant des valeurs différentes ;)

                                Alors, bien sur, tu peux créer des fonctions libres et séparer les fonctions des types de données. C'est ce qui se fait en programmation impérative (comme le C).

                                C++ autorise la programmation impérative (ainsi que la programmation générique, d'ailleurs) , et tu n'es donc absolument pas obligé d'avoir recours à la programmation orientée objets.

                                Mais d'autres langages (comme le java) ne supportent que la programmation orientée objets... Et tu n'as donc pas d'autre choix que de ... créer des classes exposant des fonctions membres et utilisant "en interne" des données membres ;)

                                Enfin, il se peut que l'on ait recours à des données (et à des fonctions) qui ne dépendent d'aucune instance particulière de la classe. Mais l'usage de ces données et de ces fonctions reste malgré tout "relativement marginal" ;)

                                Si ont peut créer de multiples instances d'une classe, c'est bien parce que ce qui caractérise une classe, c'est d'abord un certain genre de donnée et non pas un un certain genre de service.

                                On est bien d'accord. Mais une donnée qui n'est pas manipulée n'a aucun intérêt (tout comme une fonction qui n'est pas utilisée, d'ailleurs).

                                Et le problème est toujours de limiter au maximum le risque d'erreur lors de la manipulation de la donnée ;)

                                Par exemple, koala01 parlait d'une possible classe Force de RPG, qui aurait pour méthodes add(quantité),  remove(quantité), addMax(quantite), modifyMax(rapport). Ici on voit bien que ce qui caractérise cette classe (c'est à dire ce qui préside à la décision de sa création), c'est le type de donnée (la force), et non pas le service. La preuve est simple : la classe contient UN attribut, et 4 méthodes (qui toutes sont des CONSÉQUENCES du type de donnée - et non pas l'inverse). Y a-t-il une chose que j'ai mal comprise ?

                                Tu as surtout oublié le fait que, si tu dispose d'une donnée de type Force, tu vas vouloir... la manipuler.

                                Parce que pour moi, penser service, ca revient à considérer les classes comme des fonctions compliquées, donc : à quoi ca sert puisque je sais déjà écrire des fonction, qui elles, ont l'énorme avantage de ne pas devoir être instanciées ?

                                Essaye de compter le nombre d'endroits où tu souhaitera modifier la force de tes personnages.

                                Puis, rappelles toi que, dans trois mois, tu voudras sans doute la modifier encore à d'autres endroits.

                                Et, enfin, rappelle toi que, dans trois mois, tu auras eu tout le temps qu'il faut pour oublier que la force ne peut pas être négative.

                                Et tu obtiendras un tableau pour le moins embêtant:

                                Quand tu vas créer ta structure force, tu vas te dire "la force ne peut en aucun cas être négative", et tu vas donc écrire ta fonction sous la forme de

                                void foo(Force & f, int dif){
                                   /* ... */
                                   f.value = f.value - diff >0 ? f.value-dif : 0;
                                   /* ... */
                                }

                                Mais dans trois mois, comme tu auras oublié que la force ne peut pas être négative, tu vas écrire ta fonction sous la forme de

                                void bar(Force & f, int dif){
                                   /* ... */
                                   f.value-=dif;
                                   /* ... */
                                }

                                et tu t'étonneras que, à l'exécution, tu te trouves face à une force dont la valeur est ... -5, parce que tu auras appelé bar avec f.value == 5 et avec dif == 10 :p

                                En créant une fonction qui s'assure que la force ne sera jamais négative, sous une forme qui pourrait être proche de

                                void diminue(Force & f, int dif){
                                    f.value = f.value-dif> 0 ? f.value-dif : 0;
                                }

                                et en cachant à l'utilisateur (ce sera peut-être toi) le contenu de Force (donc, en lui cachant que la structure est composée d'une donnée de type int et qui est nommée value), tu ne lui laissera pas d'autre choix que de... faire appel à la fonction diminue pour diminuer la force. Et, du coup, tu éviteras qu'il ne se retrouve à écrire un code comme celui de bar.

                                En C, on va parler de "type opaques" (comme la structure FILE), en C++, on profitera des possibilités qui nous sont données de créer des fonctions membre et de placer la donnée dans l'accessibilité privée comme cela, rien à par les fonctions membre ne pourra aller chipoter à la donnée.

                                -
                                Edité par koala01 19 janvier 2018 à 14:07:48

                                • 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
                                  19 janvier 2018 à 14:20:41

                                  Umbre37 a écrit:

                                  Qu'est-ce que le couplage? Ca fait plusieurs fois que je vois le terme sans comprendre, pour moi c'est un truc de méca :)

                                  On peut definir le couplage comme une relation entre differente objets (en C++, principalement des classes et/ou structures).
                                  Plus le couplage est fort, plus la modification d'un objet aura d'impact sur ceux qui y sont reliés, et par conséquent, plus la charge de travail sera importante / difficile.

                                  Exemple:

                                  struct data
                                  {
                                      std::string name;
                                      unsigned age;
                                  }
                                  
                                  bool isOldEnough(data const& in)
                                  {
                                      return in.age > 20;
                                  }

                                  La fonction isOldEnough est fortement dépendante de la structure data.
                                  Si l'on décide que data ne doit plus stocker un age, mais une date de naissance, cela aura un impact direct sur la fonction.

                                  Par contre, si à la place d'une structure, on a une classe qui fournit le service age:

                                  class data
                                  {
                                  private:
                                      std::string mName;
                                      date mNaissance;
                                  public:
                                      unsigned age() const;
                                  }
                                  
                                  bool isOldEnough(data const& in)
                                  {
                                      return in.age() > 20;
                                  }
                                  La structure interne de la classe peux maintenant changer comme on le désire, tant qu'elle fournit le(s) service(s) attendu, cela n'aura pas d'impact sur la fonction. Le couplage (il en existe toujours un) est moindre.

                                  -
                                  Edité par Deedolith 19 janvier 2018 à 14:32:49

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    19 janvier 2018 à 14:50:46

                                    Merci beaucoup de toutes vos réponses Ksass`Peuk, koala01 et Deedolight. Je reçois un roman à chaque fois :) Je crois que je comprends un peu mieux.

                                    La notion de couplage m'aide bien à comprendre. Effectivement, limiter les dépendances entre les parties du code, c'est une bonne raison de ne pas trop utiliser de variables globales. Après pour mon ECS, c'est un détournement de l'OO pour faire autre chose, et mes composants doivent être accessibles partout ou presque. Pour faire ça efficacement sans variables globales (même si mon code fonctionne), il faudrait que j'étudie bien des patterns pour me donner des idées. J'en connais et j'en utilise parfois certains comme le singleton, CRTP, et d'autre, mais je suis globalement ignorant en la matière. (Je précise que je code en amateur, c'est pas du tout ma formation ou mon métier). Ca va me prendre pas mal de temps parce que ce sont des choses un peu difficiles à comprendre (pour moi en tout cas). Si vous avez de la bonne doc sur le sujet (selon vos critères), je suis preneur :)

                                    J'ai trouvé ça https://www.irif.fr/~yunes/cours/cpp/fiches/105-DesignPatterns.pdf

                                    et ça http://come-david.developpez.com/tutoriels/dps/ 

                                    encore merci,

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      19 janvier 2018 à 15:18:18

                                      Faut pas se précipiter sur les Design Patterns tout de suite. D'abord, écrire des tas d'applications pour se familiariser avec les concepts de la programmation objet (encapsulation, abstraction polymorphisme, modularité).  Et aussi rencontrer un certain nombre de difficultés.

                                      Les Design patterns sont des "schémas de solution" pour des problèmes qui se posent et qui ne peuvent pas se résoudre directement et proprement avec les éléments du langage. Pour les comprendre il faut avoir rencontré les problèmes avant, et avoir tenté de les résoudre par soi même, en étant conscient des faiblesses de ce qu'on a écrit (càd que ça marche, mais que ça va être difficile à maintenir, à généraliser, etc).

                                      Ceci dit les langages évoluent, et ça rend obsolète certains patterns, comme par exemple avec l'introduction des lambda-expressions.

                                      -
                                      Edité par michelbillaud 19 janvier 2018 à 15:19:18

                                      • Partager sur Facebook
                                      • Partager sur Twitter

                                      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