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 :

[conception] pattern state


Sujet :

C++

  1. #1
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut [conception] pattern state
    Bonjour,

    voilà, je vois partout le pattern state implémenté ainsi:
    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
    26
    27
    class AbstractState {
    public:
       virtual void DoSomething() = 0;
    };
     
    class ConcreteState1 : public AbstractState {
    public:
       void DoSomething() { /* implémentation */ }
    };
     
    class ConcreteState2 : public AbstractState {
    public:
       void DoSomething() { /* implémentation */ }
    };
     
    class MyObject {
    private:
       AbstractState * m_state;
     
    public:
       void ChangeState( AbstractState * newState ) { 
    	   delete m_state; 
    	   m_state = newState; 
       }
     
       void DoSomething() { m_state->DoSomething(); }
    };
    Seulement, dans certains cas cette façon de faire ne me convient pas, à cause du fait que quand on change d'état (fonction ChangeState dans mon exemple), il faut allouer un nouvel état et désallouer l'ancien. C'est un cas que j'ai rencontré récemment qui m'a fait penser à cela: une classe qui change souvent d'état, et dont les états possèdent des données => beaucoup d'allocation/désallocations qui, de plus, sont coûteuses.

    Du coup, j'ai modifié le pattern comme ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // les classes AbstractState et ConcreteStateN ne changent pas
     
    class MyObject {
    private:
    	AbstractState * m_state;
    	ConcreteState1 m_state1;
    	ConcreteState2 m_state2;
     
    public:
    	void GoToState1() { m_state = &m_state1; }
    	void GoToState2() { m_state = &m_state2; }
     
    	void DoSomething() { m_state->DoSomething(); }
    };
    Seulement, je n'ai vu cette implémentation nulle part. Et pourtant je lis beaucoup de code ces derniers temps. Je me demande donc s'il n'y aurait pas un problème que je n'ai pas vu et qui fait ce ma méthode est très mauvaise.

    Voilà, donc c'est pour savoir: qu'en pensez-vous?
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  2. #2
    screetch
    Invité(e)
    Par défaut
    Il n'y a aucun problème avec ca et c'est comme ca que chacun devrait faire a mon avis =)
    [EDIT] parler sans reflechir c'est mal... ca consomme plus de mémoire comme ca.
    Dernière modification par screetch ; 18/02/2009 à 17h59.

  3. #3
    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
    Je ne connais pas cette façon de faire. Quand j'ai besoin d'une machine à état, j'en suis resté à la bonne vieille méthode avec une matrice d'états/transitions (sauf si le nombre d'états devient trop grand, auquel cas la matrice se transforme en fonction avec switch et if imbriqués).

    J'imagine que le bénéfice de coder comme ça, c'est que tu peux rajouter des états sans toucher au code de ta classe MyObject (je n'en vois pas d'autres, et je vois plutôt plein d'inconvénients). Là, en mettant des membres dans ta classe, tu perds ce bénéfice. Du coup, autant revenir à la matrice d'états/transitions

  4. #4
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    En cherchant un peu sur le net, j'ai trouvé quelques conseils sur le pattern state qui peuvent répondre en partie à ta question :
    1/ Un état ne devrait pas contenir de variables membres : seul le comportement change,
    2/ Les états devraient être des singletons (en cohérence avec précédent) : ce qui évite ces allocations/destructions répétitives.

    Dans ton exemple, le problème que je vois, c'est que dès que tu auras un certaine nombre d'état tu vas être tenté de refaire un switch pour positionner ton état courant...

  5. #5
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    Quand tu instancies MyObject, il faut que tu initialises tous les états. Après j'ai envie de dire, ça dépend combien tu as d'états et surtout est-ce que tu vas tous les "utiliser" plusieurs fois (plusieurs boucles en fait)?
    Il faut quand même t'assurer que c'est moins coûteux comme ça...

    Si c'est pour tourner "longtemps", alors oui dans ton implémentation tu vas consommer plus de mémoire, mais tu iras plus vite!

  6. #6
    Alp
    Alp est déconnecté
    Expert éminent sénior

    Avatar de Alp
    Homme Profil pro
    Inscrit en
    Juin 2005
    Messages
    8 575
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Juin 2005
    Messages : 8 575
    Points : 11 860
    Points
    11 860
    Par défaut
    A part à rendre ça un peu plus générique, je pense que c'est pas mal

  7. #7
    screetch
    Invité(e)
    Par défaut
    au pire du pire, avec une tonne de méta prog et une lisibilité réduite, tu peux avoir un buffer contenant assez de place pour le plus gros des états et faire un placement new.
    tes états contiennent ils des variables ?

  8. #8
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Ben en fait je parlais du cas général.

    Donc si je fais une synthèse de ce qui a été dit:
    avantages de cette méthode:
    - cette méthode permet de gagner en rapidité d'exécution dans le cas où les états continennent des données (même si c'est déconseillé, ça peut être pratique).

    désavantages de cette méthode:
    - si on ajoute un état, on doit rajouter une fonction membre + une variable membre (le nouvel état) et son initialisation à la construction de l'objet.
    - si on a beaucoup d'état, le code peut devenir très lourd

    conclusion: bien quand on a pas beaucoup d'états.

    Êtes-vous d'accord?
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  9. #9
    screetch
    Invité(e)
    Par défaut
    Si tu as beaucoup de ces machines a état tu risques de consommer beaucoup plus de mémoire. tes états pèsent tous au moins 4 octets (la table de méthodes virtuelles)

  10. #10
    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
    J'ai vraiment du mal avec cette idée de mettre des variables dans une classe état.

    L'état d'une machine à état, c'est l'union de la valeur de tous ses membres. Autrement dit, tout ce qui caractérise l'état de la machine, doit être dans la classe machine.

    Sinon, je vois une grosse différence sémantique entre ce que tu fais et l'implémentation initiale :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void ChangeState( AbstractState * newState ) { 
               delete m_state; 
               m_state = newState; 
       }
    m_state est à priori un état nouvellement créé (je vois mal comment faire autrement).

    Tandis que :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
     void GoToState1() { m_state = &m_state1; }
    m_state est un ancien état, qu'on ressuscite, mais donc, avec ses anciens membres.

    Ce qui fait que l'état de ta machine est très largement complexifié, puisque l'état actuel est aussi dépendant de ton historique, et d'états non courant.

    Honnêtement, je ne vois pas comment on peut faire un minimum de preuve de fonctionnement (ce qui est quand même l'intérêt d'une machine à états) là dessus.

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

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    J'ai vraiment du mal avec cette idée de mettre des variables dans une classe état.
    L'idée du pattern state c'est de déléguer des fonction à la classe état.

    Prenons un exemple: ma classe Objet a une fonction LogState(); qui doit écrire de façon "humainement lisible" son état courant dans un logger. Et bien la classe State aura besoin d'une instance, ou d'un lien quelconque (référence, pointeur, que sais-je encore) vers le logger en question. On aura donc bien besoin d'une variable pour cela.

    Citation Envoyé par white_tentacle Voir le message
    Honnêtement, je ne vois pas comment on peut faire un minimum de preuve de fonctionnement (ce qui est quand même l'intérêt d'une machine à états) là dessus.
    En faisant comme ceci par exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     void GoToState1() 
    { 
       m_state = &m_state1; 
       m_state->Init();
    }
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  12. #12
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par r0d Voir le message
    L'idée du pattern state c'est de déléguer des fonction à la classe état.

    Prenons un exemple: ma classe Objet a une fonction LogState(); qui doit écrire de façon "humainement lisible" son état courant dans un logger. Et bien la classe State aura besoin d'une instance, ou d'un lien quelconque (référence, pointeur, que sais-je encore) vers le logger en question. On aura donc bien besoin d'une variable pour cela.
    Une std::string ne possède pas une référence vers un flux pour écrire sa valeur... Le principe est le même ici. Ton état doit pouvoir se logger dans un logger qui lui est fourni.
    En fait, à chaque état, ce que tu change, ce sont les comportements et pas les données qui caractérisent ton état. C'est pourquoi les variables dans les états c'est déroutant.

    Citation Envoyé par r0d Voir le message
    En faisant comme ceci par exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     void GoToState1() 
    { 
       m_state = &m_state1; 
       m_state->Init();
    }
    En réalité, tu devrais plutôt appelé explicitement le destructeur et faire un placement new. Mais, mon sentiment est que ça devient de la bidouille.

  13. #13
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Une std::string ne possède pas une référence vers un flux pour écrire sa valeur... Le principe est le même ici. Ton état doit pouvoir se logger dans un logger qui lui est fourni.
    En fait, à chaque état, ce que tu change, ce sont les comportements et pas les données qui caractérisent ton état. C'est pourquoi les variables dans les états c'est déroutant.


    En réalité, tu devrais plutôt appelé explicitement le destructeur et faire un placement new. Mais, mon sentiment est que ça devient de la bidouille.
    Ok si tu veux. C'était un mauvais exemple. Mais il y a des cas où c'est vraiment pratique. Peut-être évitable, mais pratique. Par exemple une machine de vente de boissons: quand elle sera en état "rendre la monnaie", il y aura plein de trucs qu'elle devra savoir dont elle n'aura pas besoin dans l'état "encaisser", comme par exemple la monnaie qu'elle a en réserve.

    Citation Envoyé par 3DArchi Voir le message
    En réalité, tu devrais plutôt appelé explicitement le destructeur et faire un placement new. Mais, mon sentiment est que ça devient de la bidouille.
    C'est jsutement ce que je cherche à éviter: appeler le destructeur (et donc le constructeur après).
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  14. #14
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par r0d Voir le message
    C'est jsutement ce que je cherche à éviter: appeler le destructeur (et donc le constructeur après).
    Soit Init fait la même chose que le destructeur et le constructeur, soit il ne fait pas la même chose. Dans ce dernier cas, cela veut dire que ton état conserve une 'mémoire', donc ce n'est plus tout à fait le même état...

  15. #15
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Il peut faire la même chose sans réallouer. Juste initialiser les variables membres. En plus, ainsi on peut se permettre de n'initialiser que celles qui ont besoin de l'être, c'est toujours ça de pris.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  16. #16
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    L'appel explicite au destructeur suivi du placement new ne fait pas d'allocation/libération mais se contente ... d'appeler le destructeur puis le constructeur.

  17. #17
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    En cherchant un peu sur le net, j'ai trouvé quelques conseils sur le pattern state qui peuvent répondre en partie à ta question :
    1/ Un état ne devrait pas contenir de variables membres : seul le comportement change,
    2/ Les états devraient être des singletons (en cohérence avec précédent) : ce qui évite ces allocations/destructions répétitives.
    Yop,

    tu saurais retrouver l'endroit où tu as lu ça? J'ai pas mal cherché mais je ne trouve rien de tel :/
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  18. #18
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par r0d Voir le message
    Yop,

    tu saurais retrouver l'endroit où tu as lu ça? J'ai pas mal cherché mais je ne trouve rien de tel :/
    Non
    Sinon, il y a le tutoriel sur le pattern Etat de Pierre Caboche

  19. #19
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    okay merci
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  20. #20
    yan
    yan est déconnecté
    Rédacteur
    Avatar de yan
    Homme Profil pro
    Ingénieur expert
    Inscrit en
    Mars 2004
    Messages
    10 033
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur expert
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2004
    Messages : 10 033
    Points : 13 968
    Points
    13 968
    Par défaut
    Salut,
    Ce qu'on fait Qt est assez sympa:
    http://qt.developpez.com/doc/4.6-sna...temachine-api/

    Ce que j'aime bien :
    * on peut éviter de faire une classe pour chaque état
    * on peut tous modifier dynamiquement et utiliser SCXML
    * l'utilisation du sytème de signal/slot qui permet de ne pas avoir de fonction DoSomething dans la classe états et qui permet de modifier dynamiquement le fonctionnement.

    Boost a sûrement quelque chose de similaire.

Discussions similaires

  1. cas d'étude: pattern state
    Par r0d dans le forum C++
    Réponses: 33
    Dernier message: 11/10/2013, 13h46
  2. Réponses: 0
    Dernier message: 04/02/2013, 14h45
  3. Combinaison de plusieurs etats avec le pattern State
    Par papaetoo dans le forum Design Patterns
    Réponses: 0
    Dernier message: 18/08/2009, 11h16
  4. [DESIGN J2ME] Utilisation du pattern STATE ?
    Par BerBiX dans le forum Java ME
    Réponses: 0
    Dernier message: 04/12/2008, 15h55

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