IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

cas d'étude: pattern state [Tutoriel]


Sujet :

C++

  1. #21
    Expert éminent
    Avatar de _skip
    Homme Profil pro
    Développeur d'applications
    Inscrit en
    Novembre 2005
    Messages
    2 898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Développeur d'applications
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Novembre 2005
    Messages : 2 898
    Points : 7 752
    Points
    7 752
    Par défaut
    Perso je serais encore un peu plus bourrin que ça :
    Implémenter la classe de façon compacte et une fois qu'elle fonctionne, écrire quelques tests unitaires sur l'interface publique.
    Ensuite on peut voir comment tu peux la refactorer, quel comportement peut être extrait ainsi que ce que ça implique en terme de passage de contexte.

    Peut être que tu te rendras compte que finalement appliquer ce pattern amène plus de problèmes qu'il n'en résout dans ce cas précis. C'est une possibilité et ça arrive assez souvent dans le code de certains stagiaires que j'ai vu travailler. Ils partent du principe que parce que c'est un DP c'est forcément la bonne solution, et au final ils tournent un truc simple en usine à gaz en ajoutant des classes et des interfaces très rigides qui nuisent plus qu'autre chose.

    On évalue la qualité d'une implémentation par sa simplicité, la clarté de son code et sa maintenabilité. Les DP sont juste des outils proposés qui peuvent aider à atteindre cet objectif dans certaines situations, rien de plus.

  2. #22
    r0d
    r0d est déconnecté
    Expert éminent

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

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 266
    Points : 6 688
    Points
    6 688
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par _skip Voir le message
    Perso je serais encore un peu plus bourrin que ça :
    Implémenter la classe de façon compacte et une fois qu'elle fonctionne, écrire quelques tests unitaires sur l'interface publique.
    Ensuite on peut voir comment tu peux la refactorer, quel comportement peut être extrait ainsi que ce que ça implique en terme de passage de contexte.

    Peut être que tu te rendras compte que finalement appliquer ce pattern amène plus de problèmes qu'il n'en résout dans ce cas précis. C'est une possibilité et ça arrive assez souvent dans le code de certains stagiaires que j'ai vu travailler. Ils partent du principe que parce que c'est un DP c'est forcément la bonne solution, et au final ils tournent un truc simple en usine à gaz en ajoutant des classes et des interfaces très rigides qui nuisent plus qu'autre chose.

    On évalue la qualité d'une implémentation par sa simplicité, la clarté de son code et sa maintenabilité. Les DP sont juste des outils proposés qui peuvent aider à atteindre cet objectif dans certaines situations, rien de plus.
    +1

    En fait, moi je vois plus les DP comme une sorte de série d'exercices. Il est bon de les comprendre et de les appliquer hors de tout contexte (juste pour tester et vérifier qu'on a bien compris), car cela permet de comprendre tout un tas de mécanismes qu'offre la poo, mais ils ne doivent pas être considérés comme des "recettes miracles". Ils sont d'ailleurs totalement passés de mode; il n'y a que moi pour écrire encore des trucs dessus (sans doute mon côté anticonformiste)

    Mais c'était d'ailleurs un peu le propos de cet article: à partir d'un D.P. on peu faire milles variations (toutes celles qu'on souhaite en fait), et il ne faut pas considérer un DP comme un "moule" dans lequel on met notre code, mais comme une sorte de guide qui donne juste des idées générales.

  3. #23
    Membre confirmé
    Avatar de Mindiell
    Profil pro
    Inscrit en
    Juin 2006
    Messages
    735
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2006
    Messages : 735
    Points : 546
    Points
    546
    Par défaut
    Noussommes d'accord, d'où mes interrogations et la solution non DP que j'ai commencé à utiliser !
    Merci encore,

  4. #24
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2008
    Messages
    832
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Décembre 2008
    Messages : 832
    Points : 2 621
    Points
    2 621
    Par défaut
    Je suis en train de chercher des infos sur diverses implémentations du state pattern, et donc suis tombé sur cet article.

    Il y a plusieurs points que j'aimerai commenter à son sujet:

    * modèle initial:
    "Une autre raison pour laquelle je n'aime pas trop cette implémentation c'est qu'elle pose un problème (un cas de conscience au moins) si l'on souhaite que les états puissent accéder à des données/fonctions non publiques du contexte."

    Problème solvable très simplement (je suis surpris de n'avoir vue cette méthode nulle part jusqu'a présent d'ailleurs):
    Mettre la classe ancêtre en amie de la classe de contexte et mettre dans la classe State les getters/setters souhaités. Cela permets de donner aux classes concrètes un accès limité aux données de la classe Context:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    class Context
    {
      friend class State;
      int m_i;
      int getI( void ) const;
      void setI( int i );
    };
    J'utilise à l'origine cette astuce pour le design pattern visitor ( je n'aime pas trop l'idée de mettre des attributs en accès public... ) mais ça fonctionne aussi pour les machines à état, je pense.

    Il ne reste donc en inconvénient que celui de la dépendance de l'état abstrait vers le contexte, les autres états ayant uniquement besoin de connaître le nom du contexte. Pas de #include "context.hpp", mais "class Context;" malgré tout nécessaire, donc.

    * YATUS:
    Une façon d'éviter la recompilation de Game dès qu'on ajoute un état, serait d'utiliser un conteneur de pointeurs de GameState, par exemple std::vector<GameState*> (l'utilisation de smart pointers serait plus qu'appropriée ici, mais c'est pour l'exemple) qui soit initialisé dans le constructeur.
    Ainsi, la déclaration de Game n'aurait pas à dépendre aussi fortement des états ( bien que ça ne change rien pour l'implémentation ) et on évite nombre de recompilations à la moindre modification/ajout d'un état ( par exemple, on évite la recompilation de main() dans l'exemple ).

    * Les états sous forme de singleton
    Faire hériter chaque état concret d'une classe singleton ( donc, double héritage) évite de refaire la logique singleton à chaque fois.
    Vu qu'on travaille avec des références, il serait aussi possible de supprimer 2 des 3 références/pointeurs: quel intérêt y a-t-il à conserver des références vers des variables globales ( aka singleton ) que l'on peut récupérer de toute façon via une fonction ( méthode statique de la classe singleton )?

    Note de fin:
    Pourquoi ne pas utiliser les surcharges d'opérateurs de cast et de constructeurs pour gérer les transitions?
    Si je veux pouvoir transformer un état A en état B, un état B en état A ou C, et un état C en objet B, il suffit d'écrire les opérateurs de cast et/ou constructeurs correspondant, après tout.
    Si on utilise une construction à base de RAII ( le point fort du C++ par rapport à nombre de langages, selon moi ) on peut ainsi se passer de devoir implémenter des classes dédiées à cette tâche.
    Quant au "problème" des new/delete, je ne le vois pas: il n'y a pas de données, donc la construction est très rapide, non?

  5. #25
    r0d
    r0d est déconnecté
    Expert éminent

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

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 266
    Points : 6 688
    Points
    6 688
    Billets dans le blog
    2
    Par défaut
    Salut Freem, merci pour tes remarques.
    Je vais te donner mon point de vue sur tout ça, mais je ne prétend pas détenir la vérité. Surtout qu'il faut garder à l'esprit que si les patrons de conception tentent de dresser des règles universelles, chaque programme est un cas particulier, donc différent. Par conséquent, quelque chose peut être considéré comme mauvais dans le cas général, mais peut être bon dans un cas particulier. Et inversement.

    Citation Envoyé par Freem Voir le message
    * modèle initial:
    "Une autre raison pour laquelle je n'aime pas trop cette implémentation c'est qu'elle pose un problème (un cas de conscience au moins) si l'on souhaite que les états puissent accéder à des données/fonctions non publiques du contexte."

    Problème solvable très simplement (je suis surpris de n'avoir vue cette méthode nulle part jusqu'a présent d'ailleurs):
    Mettre la classe ancêtre en amie de la classe de contexte et mettre dans la classe State les getters/setters souhaités.
    C'est justement ça le "cas de conscience" dont je parle. Certains développeurs (dont moi) n'aiment pas utiliser de friend. C'est un peu comme les pointeurs, ou les variables membres mutables: à éviter tant que faire se peut. friend respecte l'encapsulation mais casse un peu les mécanismes sémantiques.

  6. #26
    r0d
    r0d est déconnecté
    Expert éminent

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

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 266
    Points : 6 688
    Points
    6 688
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par Freem Voir le message
    * YATUS:
    Une façon d'éviter la recompilation de Game dès qu'on ajoute un état, serait d'utiliser un conteneur de pointeurs de GameState, par exemple std::vector<GameState*> (l'utilisation de smart pointers serait plus qu'appropriée ici, mais c'est pour l'exemple) qui soit initialisé dans le constructeur.
    Ainsi, la déclaration de Game n'aurait pas à dépendre aussi fortement des états ( bien que ça ne change rien pour l'implémentation ) et on évite nombre de recompilations à la moindre modification/ajout d'un état ( par exemple, on évite la recompilation de main() dans l'exemple ).
    Effectivement, si le temps de compilation est important, alors oui c'est une bonne solution. Sinon, encore une fois, je préfère éviter l'utilisation de pointeurs.

  7. #27
    r0d
    r0d est déconnecté
    Expert éminent

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

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 266
    Points : 6 688
    Points
    6 688
    Billets dans le blog
    2
    Par défaut
    Enfin, concernant les derniers points, il faut comprendre une chose: contrairement à la théorie, dans la vraie vie un état contient souvent des données.

    Prenons l'exemple d'un jeu de plateforme: le personnage aura disons 3 états: marche, saut et attaque. On n'est pas obligé d'utiliser une machine à état pour faire ça, mais c'est une solution possible; en particulier c'est fort pratique pour gérer les animations et les transitions entre animations.

    Déjà, si le jeu est multijoueur, alors utiliser des singletons pour les états n'est pas possible, puisque chaque personnage a ses propres états.
    Ensuite, chaque état aura des données qui, justement, caractérise cet état. Par exemple, l'état "marche" aura besoin de données du type 'direction', 'vitesse' ou encore des informations concernant l'animation (sprite courant), ce genre de choses.

    Dans d'autres situations, par exemple dans le cas de logiciels qui commandent des robots complexes (informatique industrielle), alors les états peuvent avoir beaucoup, beaucoup plus de données (je pense notamment aux données relatives aux capteurs). Et d'un point de sémantique, ça n'a pas de sens de faire remonter ces données au niveau du contexte, ni même de la super-classe State, parce que ces données sont spécifiques à chaque état. On peut encapsuler ces données dans une structure qui sera agrégée par les états concret, mais le problème reste le même: un new peut être coûteux, et surtout en terme de code (le problème de la mémoire est très rarement important).

  8. #28
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    Citation Envoyé par r0d Voir le message
    C'est justement ça le "cas de conscience" dont je parle. Certains développeurs (dont moi) n'aiment pas utiliser de friend. C'est un peu comme les pointeurs, ou les variables membres mutables: à éviter tant que faire se peut. friend respecte l'encapsulation mais casse un peu les mécanismes sémantiques.
    Pareil, par contre je me demandais si passer State en class interne a Context ne resoudrai pas le probleme? Les classes internes peuvent acceder a leur class encapsulante, mais je ne sais plus si c'est vrai pour leurs derivees...

  9. #29
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    Citation Envoyé par r0d Voir le message
    Enfin, concernant les derniers points, il faut comprendre une chose: contrairement à la théorie, dans la vraie vie un état contient souvent des données.

    Prenons l'exemple d'un jeu de plateforme: le personnage aura disons 3 états: marche, saut et attaque. On n'est pas obligé d'utiliser une machine à état pour faire ça, mais c'est une solution possible; en particulier c'est fort pratique pour gérer les animations et les transitions entre animations.

    Déjà, si le jeu est multijoueur, alors utiliser des singletons pour les états n'est pas possible, puisque chaque personnage a ses propres états.
    Ensuite, chaque état aura des données qui, justement, caractérise cet état. Par exemple, l'état "marche" aura besoin de données du type 'direction', 'vitesse' ou encore des informations concernant l'animation (sprite courant), ce genre de choses.

    Dans d'autres situations, par exemple dans le cas de logiciels qui commandent des robots complexes (informatique industrielle), alors les états peuvent avoir beaucoup, beaucoup plus de données (je pense notamment aux données relatives aux capteurs). Et d'un point de sémantique, ça n'a pas de sens de faire remonter ces données au niveau du contexte, ni même de la super-classe State, parce que ces données sont spécifiques à chaque état. On peut encapsuler ces données dans une structure qui sera agrégée par les états concret, mais le problème reste le même: un new peut être coûteux, et surtout en terme de code (le problème de la mémoire est très rarement important).
    Une note a propos de tout ca: la premiere fois que j'ai essaye Boost.MSM, le premier test que j'ai fais c'etait de verifier le comportement d'une FSM cree avec cette bibliotheque si je la mets dans un vector.
    Je sais plus exactement pourquoi mais ca crash VC10 a la compilation...

    Bref, tout ca pour dire que mon cas habituel maintenant c'est que j'ai des tas d'agents "betes" -dont le comportement peut etre represente par une FSM-, et que je veux pouvoir updater leur comportement "a la chaine" ce qui fais que la representation de l'agent et de son FSM sont dans des vector/array que je parcours d'une traite lors d'updates.

    Pour souligner ce que tu disais, c'est effectivement tres courant dans les moteurs de jeux video surtout ceux bases sur des "composants" constituant les differentes entitees de jeux. Chaque entitee est definie par un agregat specifique d'instances differents types de composants, mais chacune de ces instances sont, techniquement dans des tableaux specifique a chaque type de composant. Cela permet, entre autre, de la parellelisation d'update.

  10. #30
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 632
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Salut,
    Citation Envoyé par r0d Voir le message
    C'est justement ça le "cas de conscience" dont je parle. Certains développeurs (dont moi) n'aiment pas utiliser de friend. C'est un peu comme les pointeurs, ou les variables membres mutables: à éviter tant que faire se peut. friend respecte l'encapsulation mais casse un peu les mécanismes sémantiques.
    A vrai dire, j'ai du mal à être d'accord avec tes réticences (bien que je puisse les comprendre).

    Quand elle est utilisée à bon escient, l'amitié est une alternative qui te permet d'éviter d'avoir à exposer certaines données ou fonctions qui, sans elles, finiraient en accessibilité publique, et seraient donc accessibles par n'importe qui (et donc... utilisées sans doute à mauvais escient à des endroits où elles ne devraient pas l'être).

    Il ne faut bien sur pas en arriver à devoir déclarer dix amitiés envers une seule et même classe, mais si tu déclares une classe amie d'une autre, cela va, au contraire, améliorer grandement ton encapsulation.

    Ceci dit, je peux comprendre, comme je te l'ai dit, tes réticences, dans le sens où tu pourrais estimer que cela rajoute une dépendance entre ces deux classes.

    Mais il faut aussi raison garder: la dépendance sera de toutes manières présente à partir du moment où tu décideras de permettre à ton état d'accéder aux données ou aux fonctions de ton contexte

    @Freem >> Je suis relativement contre le fait de fournir un couple de mutateur et d'accesseur sur une donnée.

    Si l'accesseur peut se justifier dans le sens où l'on peut considérer qu'il s'agit bel et bien d'un service fourni par la classe, le mutateur permet de modifier une donnée depuis "n'importe où" car il expose la donnée aussi surement que si elle était en accessibilité publique.

    Etant donné que l'on peut considérer que la donnée ne devrait sans doute être modifiée qu'au travers de l'état (ou au travers d'autres actions bien spécifiques) l'amitié tend à devenir une alternative des plus valables dans le sens où elle permettra, justement, de modifier "sans contrôle excessif" une donnée du contexte au niveau de l'état, tout en évitant que cette exception ne finisse par être pratique courante dans le reste du code

  11. #31
    r0d
    r0d est déconnecté
    Expert éminent

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

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 266
    Points : 6 688
    Points
    6 688
    Billets dans le blog
    2
    Par défaut
    Tout d'abord, comme je disais, j'évite d'utiliser friend, mais cela ne veut pas dire que je ne l'utilise jamais.

    Et si je n'aime pas l'utiliser, ce n'est pas pour un raison de dépendance, car effectivement, comme tu l'as dit, la dépendance est là de toutes façons.

    Non, si je n'aime pas utiliser friend, c'est à cause de la sémantique du code. Prenons un exemple: une classe Car (voiture), qui a deux caractéristiques: vitesse et direction. Cette classe est conçue dans le but de se débrouiller toute seule: pour l'affichage (fonction Draw()), on lui passe le contexte et elle s'affiche "toute seule":
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Car
    {
    public:
       void Draw( sf::RenderWindow & window );
     
    private:
       Direction direction_;
       double speed_;
    };
    C'est en quelque sorte de la programmation par service. La classe Car rend le service qui est celui de s'afficher dans la fenêtre donnée.

    Maintenant on voudrait que la voiture possède également un état courant, parmi 3 états possibles: Normal, Damaged (abimé) et InFire (en feu).
    Les états Damaged et InFire ont besoin de connaître la vitesse et la direction pour afficher la fumée et les flammes de la façon la plus réaliste possible. Alors on ajoute les états et on les met en friend pour accéder à speed_ et direction_:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class Car
    {
       friend CarState;
    public:
       void Draw( sf::RenderWindow & window );
     
    private:
       CarState* current_state_;
     
    private:
       Direction direction_;
       double speed_;
    };
     
    class CarState 
    {
    protected:
       Car* owner;
       inline const Direction & GetCarDirection() const { return owner_->direction_; }
       inline const double & GetCarSpeed() const { return owner_->speed_; }
    };
     
    class NormalCarState : public CarState { /* code */ };
    class DamagedCarState : public CarState { /* code */ };
    class InFireCarState : public CarState { /* code */ };
    Notez que GetCarDirection et GetCarSpeed sont implémentés dans la classe de base des états afin d'être utilisables dans tous les états qui en héritent (pas besoin, ainsi, de déclarer tous les états comme amis).
    Notez aussi que ce sont deux accesseurs, et que les accesseurs c'est le mal; on commence donc à s'approcher des enfers.

    Ensuite, on veut que nos états puissent modifier la vitesse de la voiture. Par exemple, lorsqu'elle est en feu, alors on veut que la voiture diminue sa vitesse jusqu'à s'arrêter. Alors il faut implémenter des mutateurs dans la classe CarState! Quelle horreur!

    ça va ressembler à ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class CarState 
    {
    protected:
       Car* owner;
       inline const Direction & GetCarDirection() const { return owner_->direction_; }
       inline const double & GetCarSpeed() const { return owner_->speed_; }
     
       inline void SetCarDirection( const Direction & direction ) { owner_->direction_ = direction; }
       inline void SetCarSpeed( const Direction & speed) { owner_->speed_ = speed; }
    };
    Déjà c'est super moche, là on est aux portes des enfers. Mais si en plus, on veut gérer d'autres choses lorsqu'on modifie la vitesse de la voiture, comme par exemple envoyer des events au moteur audio, alors ça devient n'importe quoi: on voudra gérer ça dans la classe CarState, mais dès qu'on voudra changer la vitesse d'une autre façon, par exemple sur un event du controller (action du joueur par exemple), alors on est niqué! Dans les flammes de l'enfer! Et on en est arrivé là parce que - et c'est où je voulais en venir - on a pas fait attention à la sémantique: en donnant l'amitié à la classe CarState, on lui permet de s'occuper de choses dont elle n'a pas à s'occuper.

    C'est une erreur classique, et pour généraliser, le problème de l'amitié c'est qu'on effectue un transfert de responsabilité, et de façon assez peu explicite, ce qui ouvre la porte à certaines fenêtres.

  12. #32
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 632
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Citation Envoyé par r0d Voir le message
    Notez aussi que ce sont deux accesseurs, et que les accesseurs c'est le mal; on commence donc à s'approcher des enfers.
    A vrai dire, les accesseurs sont surtout le mal lorsqu'ils ne correspondent pas à un service que l'on est en droit d'attendre de la part de la classe, ou lorsqu'ils nous obligent à avoir la connaissance d'un type "complexe" alors que l'on pourrait se passer de cette connaissance.
    Ensuite, on veut que nos états puissent modifier la vitesse de la voiture. Par exemple, lorsqu'elle est en feu, alors on veut que la voiture diminue sa vitesse jusqu'à s'arrêter. Alors il faut implémenter des mutateurs dans la classe CarState! Quelle horreur!

    ça va ressembler à ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class CarState 
    {
    protected:
       Car* owner;
       inline const Direction & GetCarDirection() const { return owner_->direction_; }
       inline const double & GetCarSpeed() const { return owner_->speed_; }
     
       inline void SetCarDirection( const Direction & direction ) { owner_->direction_ = direction; }
       inline void SetCarSpeed( const Direction & speed) { owner_->speed_ = speed; }
    };
    Déjà c'est super moche, là on est aux portes des enfers. Mais si en plus, on veut gérer d'autres choses lorsqu'on modifie la vitesse de la voiture, comme par exemple envoyer des events au moteur audio, alors ça devient n'importe quoi: on voudra gérer ça dans la classe CarState, mais dès qu'on voudra changer la vitesse d'une autre façon, par exemple sur un event du controller (action du joueur par exemple), alors on est niqué! Dans les flammes de l'enfer! Et on en est arrivé là parce que - et c'est où je voulais en venir - on a pas fait attention à la sémantique: en donnant l'amitié à la classe CarState, on lui permet de s'occuper de choses dont elle n'a pas à s'occuper.

    C'est une erreur classique, et pour généraliser, le problème de l'amitié c'est qu'on effectue un transfert de responsabilité, et de façon assez peu explicite, ce qui ouvre la porte à certaines fenêtres.
    Pour cela par contre, je ne peux forcément qu'être d'accord avec toi

  13. #33
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par r0d Voir le message
    Ensuite, on veut que nos états puissent modifier la vitesse de la voiture. Par exemple, lorsqu'elle est en feu, alors on veut que la voiture diminue sa vitesse jusqu'à s'arrêter. Alors il faut implémenter des mutateurs dans la classe CarState! Quelle horreur!
    Yep. friend devrait s’hériter, ce qui permettrait de mettre les mutateurs en privé directement dans car. À ce titre, j’aime beaucoup le mécanisme d’accessibilité d’Eiffel (définition individuelle de qui peut appeler chaque méthodes, à partir d’une classe de base).

  14. #34
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2008
    Messages
    832
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Décembre 2008
    Messages : 832
    Points : 2 621
    Points
    2 621
    Par défaut
    En effet, j'évite friend comme la peste aussi. Mais je préfère utiliser friend plutôt que mettre une propriété en accès public.
    Il me semble évident qu'il faut réfléchir à deux fois avant d'accéder à un attribut d'une autre classe (que ce soit via friend ou un attribut public), puis supprimer ce qu'on a fait, et re réfléchir 2 fois.

    Quant à l'intérêt de modifier des données du contexte en fonction de l'état, hé bien, je ne suis pas encore très à l'aise avec pattern (de la même façon et pour les mêmes raisons qu'avec le pattern visiteur en fait: accès à des données internes par un étranger), donc c'est peut-être une mauvaise idée.

    Je crois que l'exemple que j'avais en tête était l'init une connexion d'un client à un serveur, ou le client doit:
    1) se faire accepter et reconnaître
    2) se synchroniser (et n'avoir qu'une partie des données, il est inutile et même dangereux de dire à tout le monde l'état exact et complet du jeu: si un ennemi est invisible par exemple, cela permettrait une tricherie tout ce qu'il y a de plus simple)
    3) faire son boulot
    4) tout en pouvant informer d'un problème de connexion
    Enfin, c'est peut-être tout bêtement le mauvais dp pour cette tâche.

    Citation Envoyé par koala01 Voir le message
    @Freem >> Je suis relativement contre le fait de fournir un couple de mutateur et d'accesseur sur une donnée.

    Si l'accesseur peut se justifier dans le sens où l'on peut considérer qu'il s'agit bel et bien d'un service fourni par la classe, le mutateur permet de modifier une donnée depuis "n'importe où" car il expose la donnée aussi surement que si elle était en accessibilité publique.
    Honnêtement? Moi aussi.
    Je suis d'ailleurs, et ai toujours été, contre l'idée même d'accesseurs: connaître un accesseur signifie connaître l'état interne d'une autre classe, qui n'est donc plus la seule experte.
    A l'époque ou l'on a tenté de m'enseigner la POO (échec critique je dirais, même si j'ai fini par acquérir la logique plus tard) les profs disaient de tout mettre en private et de coller des accesseurs publics à chaque fois. Ou c'est ainsi que j'ai compris leurs cours, et ayant un passif C (débutant, ça ne faisait que 3 ans que je programmais après tout) je ne voyais pas l'intérêt de ces lignes de code en rab. Je n'aime toujours pas, mais pour d'autres raisons, sûrement meilleures .

    C'était juste pour mettre en valeur le fait que l'on pouvait utiliser friend en combinaison avec un mécanisme (getter/setter dans l'exemple) pour cacher l'accès direct aux données, tout en permettant à un état d'altérer le contexte, sans coller les propriétés de ce contexte en public. C'est une solution du moins pire, si vous voulez.
    Après, il y a d'autres alternatives. Mettre les états en ami un par un au cas par cas par exemple... n'est-ce pas plus lourd voire même pire? Parce que dans cette solution, il faut ajouter au contexte un ami à chaque nouvel état altérant ou simplement accédant, et en plus l'expertise se retrouve dans chaque état directement, plutôt que via l'interface fournie par l'ancêtre, qui aura été écrit en même temps que le contexte et par la même personne, qui peut donc décider ( et documenter) que seul un nombre restreint de propriétés doivent être accessibles ( lecture, écriture ou les deux ) par les états.
    Et ce n'est jamais accessible par autre chose que les états, contrairement à si on avais mis en public les propriétés, donc qualifier d'infernale cette technique me semble un poil exagéré éventuellement, d'apostate, ou de blasphématoire, mais pas infernale ^^

    Citation Envoyé par r0d Voir le message
    Effectivement, si le temps de compilation est important, alors oui c'est une bonne solution. Sinon, encore une fois, je préfère éviter l'utilisation de pointeurs.
    J'aurai été d'accord avec toi il y a 3 ans. Avant C++11. Qui plus est, il existe certains outils (genre boost::pointer_container ou un truc du genre) qui permettent de faire comme si ce n'étaient pas des pointeurs. Conteneurs polymorphiques en somme.
    Et donc, pas de pointeurs (apparents du moins), et pas de recompilation. Bon, certes, cet outil à un coût au runtime, à cause des dynamic_cast internes de mémoire... mais bon, ça s'optimise quand ça deviens un problème ça.

    Citation Envoyé par r0d Voir le message
    Enfin, concernant les derniers points, il faut comprendre une chose: contrairement à la théorie, dans la vraie vie un état contient souvent des données.
    <snip>
    Déjà, si le jeu est multijoueur, alors utiliser des singletons pour les états n'est pas possible, puisque chaque personnage a ses propres états.
    <snip>
    alors les états peuvent avoir beaucoup, beaucoup plus de données (je pense notamment aux données relatives aux capteurs)
    Tout à fait d'accord. Si l'état est une donnée en lui-même, il n'en reste pas moins une donnée composée.
    Mais pour reprendre ton exemple de jeu, ces états altèrent tous une propriété "position" j'imagine, qui elle appartient au personnage (contexte).

    Pour le fait qu'un état puisse contenir beaucoup de données, ça ne me choque pas. Cependant, il existe d'autres DP pour les problèmes de mémoire / temps de construction. En vrac:
    _ factory
    _ prototype
    _ builder
    _ flywieght
    Et peut-être même d'autres. Combiner les pattern entre eux ne me pose absolument aucun souci, je dirai même que certains semblent faits pour ça. Je peux me planter, mais dans le cas de la multitude de données, ça ressemble à vue de nez (mais sans contexte exact, impossible de le dire) à un usage de builder.

    Toujours est-il que le but d'utiliser les constructeurs et opérateurs de cast était surtout pour fournir un moyen simple d'offrir les transitions en exploitant les capacités du C++.
    Si je reprend l'exemple du jeu:
    on passe d'un état "marche" à "statique" => m_state = StaticState( m_state );
    on passe d'un état "saute" à "statique" => m_state = StaticState( m_state );

    0 changement donc. Autre point intéressant: pas besoin de vérifier nous-même la possibilité de passer d'un état à l'autre: le compilateur le fait nous même: si le bon constructeur ou operateur de cast manque, alors ça ne compilera juste pas.
    C'est juste une possibilité supplémentaire

    (note a moi-même, il va falloir que je pense a activer le suivi auto par mail, vu que je passe beaucoup moins souvent qu'avant ... )

Discussions similaires

  1. Cas d'étude : Java et fuite mémoire
    Par Bobak42 dans le forum Langage
    Réponses: 8
    Dernier message: 04/09/2012, 15h55
  2. [conception] pattern state
    Par r0d dans le forum C++
    Réponses: 21
    Dernier message: 28/09/2009, 14h44
  3. Combinaison de plusieurs etats avec le pattern State
    Par papaetoo dans le forum Design Patterns
    Réponses: 0
    Dernier message: 18/08/2009, 12h16
  4. [DESIGN J2ME] Utilisation du pattern STATE ?
    Par BerBiX dans le forum Java ME
    Réponses: 0
    Dernier message: 04/12/2008, 16h55

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo