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 : loi de Demeter + ISP


Sujet :

C++

  1. #1
    Membre du Club
    Profil pro
    Enseignant
    Inscrit en
    Septembre 2011
    Messages
    43
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Septembre 2011
    Messages : 43
    Points : 46
    Points
    46
    Par défaut Conception : loi de Demeter + ISP
    Bonjour à tous,

    Je reviens avec un problème similaire à celui ci : http://www.developpez.net/forums/d16...t-get-classes/ dont j'étais déjà l'auteur.

    Pour résumé et vous éviter de (re)lire l'ancien post, mon projet se compose de :

    * une petite dizaines de classes A, B, C ... qui représentent des objets spécialisés.

    * une classe "core" qui s'occupe de faire évoluer en fonction du temps ma dizaine d'objets issus des classes A, B, C ...

    * une classe "ui"
    qui s'occupe de gérer les interactions clavier/souris/joysticks qui utilise "core"
    par exemple, l'appui sur une touche du clavier exécute la fonction correspondante à l'action codée dans "core" représentée par la touche.

    * une classe "menu"
    qui s'occupe d'un menu textuel qui utilise "core"
    j'ai un menu qui est composé d'une liste de commandes (représentées par des strings). lorsque l'on sélectionne une commande, la classe menu exécute la fonction correspondante à l'action codée dans "core" représentée par la string.

    * une classe "Script_Executor"
    qui s'occupe de gérer un langage de script fait maison qui utilise "core" (décrite schématiquement ci dessous)
    par exemple, la lecture d'une ligne de script exécute la fonction correspondante à l'action codée dans "core" représentée par la ligne de script correspondante.


    ma classe "core" ressemble à 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
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
     
    // A, B , C , D... environ 10 classes ayant chacune 3 à 7 paires de get/set et d'appels de fonctions publiques à la classe en question.
     
    class A
    {
    private:
        double m_a1;
        int    m_a2;
        bool   m_a3;
    public:
        A() : m_a1(0), m_a2(0), m_a3(false) {}
        double get_a1() const    {return m_a1;}
        int    get_a2() const    {return m_a2;}
        bool   get_a3() const    {return m_a3;}
        void   set_a1(double a1) {m_a1 = a1;}
        void   set_a2(int    a2) {m_a2 = a2;}
        void   set_a3(bool   a3) {m_a3 = a3;}
        void fctA1(...) {...}
        void fctA2(...) {...}
    };
     
    class B
    {
    private:
        double m_b1;
        double m_b2;
        bool   m_b3;
    public:
        B() : m_b1(0), m_b2(0), m_b3(false) {}
        double get_b1() const    {return m_b1;}
        double get_b2() const    {return m_b2;}
        bool   get_b3() const    {return m_b3;}
        void   set_b1(double b1) {m_b1 = b1;}
        void   set_b2(double b2) {m_b2 = b2;}
        void   set_b3(bool   b3) {m_b3 = b3;}
        void fctB1(...) {...}
        void fctB2(...) {...}
    };
     
    // core a une instance de chacune des classes ci-dessus
     
    class core
    {
    private:
        A m_a;
        B m_b;
        // ...
        //fonctions spécifiques et privées de core
    public:
        double get_a1() const    {return m_a.get_a1();}
        int    get_a2() const    {return m_a.get_a2();}
        bool   get_a3() const    {return m_a.get_a3();}
        double get_b1() const    {return m_b.get_b1();}
        double get_b2() const    {return m_b.get_b2();}
        bool   get_b3() const    {return m_b.get_b3();}
        // ...
        void   set_a1(double a1) {m_a.set_a1(a1);}
        void   set_a2(int    a2) {m_a.set_a2(a2);}
        void   set_a3(bool   a3) {m_a.set_a3(a3);}
        void   set_b1(double b1) {m_b.set_b1(b1);}
        void   set_b2(double b2) {m_b.set_b2(b2);}
        void   set_b3(bool   b3) {m_b.set_b3(b3);}
        // ...
        void fctA1(...) { A.fct1(...);}
        void fctA2(...) {A.fct2(...);}
        void fctB1(...) {B.fct1(...);}
        void fctB2(...) {B.fct2(...);}
        void fctB3(...) {B.fct3(...);}
        void fctB4(...) {B.fct4(...);}
     
        // ...
     
       //fonctions spécifiques et publiques de core
    };
     
    class Script_Executor
    {
    private:
        core& m_core;
        // ...
    public:
        Script_Executor(core& param) : m_core(param) {}
        double get_a1() const    {return m_core.get_a1();}
        int    get_a2() const    {return m_core.get_a2();}
        bool   get_a3() const    {return m_core.get_a3();}
        double get_b1() const    {return m_core.get_b1();}
        double get_b2() const    {return m_core.get_b2();}
        bool   get_b3() const    {return m_core.get_b3();}
        // ...
        void   set_a1(double a1) {m_core.set_a1(a1);}
        void   set_a2(int    a2) {m_core.set_a2(a2);}
        void   set_a3(bool   a3) {m_core.set_a3(a3);}
        void   set_b1(double b1) {m_core.set_b1(b1);}
        void   set_b2(double b2) {m_core.set_b2(b2);}
        void   set_b3(bool   b3) {m_core.set_b3(b3);}
        // ...
    };
    En gros, j'ai trois classes qui utilisent massivement les fonctions de la classe code qui elle s'occupe des 10 objets.

    J'avais déjà demandé de l'aide parce que core était embouteillé par trop de fonctions getClasseAMachin1() setClasseBMachin3() ... au point d'être illisible.

    C'est toujours le cas.

    J'ai écouté vos remarques, acheter le livre "coder efficacement Bonnes pratiques et erreurs à éviter (en C++)" et je me suis retroussé les manches.
    La lecture du livre m'a permis entre autre de prendre connaissance de la loi de Demeter et des principes S.O.L.I.D. (au point de changer ma vie de programmeur. . Vraiment !!)

    Maintenant mon application est un véritable chantier. Mon code se "clarifie" même si mes collègues voient surtout que je me focalise sur l'existant sans rien apporter de nouveau sur le court terme. Ce qui n'est pas la question.

    J’ai parcouru mon projet, et j'ai recherché à appliquer le plus possible la loi S de SRP. Ce fut très enrichissant et intellectuellement stimulant.
    J'ai modifié les RTTI qui servaient de tests de base en grand nombre.
    Je suis allé à la chasse aux set/get discutables en m’efforçant de penser aux comportements et pas qu'aux données.

    Mais je bloque sur le principe ISP.

    Dans mon projet clairement, "core" apparaît comme un "god object" qui contient tout et qui s'occupe de tout. Je suis arrivé à une classe qui gère synchronise ses objets (en fonction du temps) ET qui propose aux autres classes utilisatrices "ui", "menu" et "Script_Executor" des fonctions passerelles afin de respecter la loi de Demeter. Typique:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    void core::flipTimeClasseA() {
       return classeA->flipTime;
    }
    dans mes classes utilisatrices, j'ai par exemple la fonction:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    core->flipTimeClasseA();
    Voila pour la présentation de mon problème. (Désolé de la longueur !!!! )

    Des solutions ?
    J'ai cherché, je ne suis pas resté passif:

    - j'ai cassé la loi de Demeter sur 2-3 objets [pour tester] et réalisé un

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    ClasseA* getClasseA() {
    return classeA;
    }
    dans mes classes utilisatrices, j'ai écris :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    core->getClasseA()->flipTime();
    Option Core c'est le top, j'ai 15 fonctions en moins! Si je continue ainsi après 10 getClasseXXX, mon fichier " core" est enfin dépollué de tous ce qui m’embêtait alors. D'un point de vu personnel, j'ai rendu l'écriture du code plus complexe. Pour programmer la suite de l'application, il est nécessaire alors d'avoir en tête toutes les fonctions publiques ... la portée des fonctions étaient avant d'un niveau (exemple: classeA vers core) et passe maintenant à 2 niveaux (exemple: classeA vers Core vers UI) . Je suis en défaut par rapport au DIP
    Je n'en suis pas content.


    - je crée une classe "core_acess"
    C'est à dire une classe vide amie avec "core" qui reprend toutes les fonctions passerelles de core en accédant à la partie privée de "core" et qui sera alors l'intermédiaire entre "core" et les classes utilisatrices "Script_Executor", "menu", "ui".

    Je n'en suis pas content non plus. j'ai l'impression alors de déplacer mon problème ailleurs et de ne pas mieux coller au principes du S.O.L.I.D.


    Qu'en pensez vous ?
    Que feriez vous à ma place ?

    PS: merci d'être arrivé au bout de mon post, j'ai moi même cru ne pas y arriver.

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 628
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 628
    Points : 10 553
    Points
    10 553
    Par défaut
    Je sors le pop-corn parce que cela sent le fil de discussion fleuve

    Sinon Koala01 te dirait Manager" ne veut rien dire à vue de nez, je dirais plus Script_Executor

  3. #3
    Membre du Club
    Profil pro
    Enseignant
    Inscrit en
    Septembre 2011
    Messages
    43
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Septembre 2011
    Messages : 43
    Points : 46
    Points
    46
    Par défaut
    Non non, attention !
    Je ne veux pas être l'auteur d'une discussion fleuve hen !!

    J'ai juste un sacré problème d'organisation de mon code (qui n’effraye pas les autres développeurs plus que ça) et qui me pénalise dans mon travail.
    et ce problème je veux le régler

    Alors on peut laisser les flingues dehors, et régler cela de manière pacifique

  4. #4
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 113
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 113
    Points : 32 960
    Points
    32 960
    Billets dans le blog
    4
    Par défaut
    Est-ce que les éléments externes ont connaissance de ce qu'ils doivent appeler dans Core ou juste de Core ?
    Typiquement pour l'UI et les inputs, on a une classe InputsManager dans laquelle on peut donner des callbacks à appeler sur chaque évènement et la fonction qui envoit l'avènement n'a aucune idée de qui va réellement l'intercepter et quelles actions vont en découdre.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  5. #5
    Membre du Club
    Profil pro
    Enseignant
    Inscrit en
    Septembre 2011
    Messages
    43
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Septembre 2011
    Messages : 43
    Points : 46
    Points
    46
    Par défaut
    Est-ce que les éléments externes ont connaissance de ce qu'ils doivent appeler dans Core ou juste de Core ?

    Non aucune connaissance des éléments externes. Seul Core connait le tout.

  6. #6
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 113
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 113
    Points : 32 960
    Points
    32 960
    Billets dans le blog
    4
    Par défaut
    Si Core est sensé servir de Dispatcher, pourquoi t'amuser à casser l'encapsulation en ajoutant des accesseurs aux classes qu'il encapsule et utiliser ces accesseurs pour appeler directement la classe concernée depuis le code appelant ?
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  7. #7
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 068
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 068
    Points : 12 111
    Points
    12 111
    Par défaut
    Avec le vrai nom des classes et des méthodes, ça serait moins abstrait.

    Je ne comprends pas pourquoi vous avez l'impression que la classe "Core" devient un "God Object".

    Votre description fait de lui la classe d'interface entre votre code métier et le code d'IHM/Scripting.

    Les classes d'IHM et de Scripting n'ont pas à connaitre les classes A-B-... qui implémentent le métier. Seul l'API présentée par "Core" n'a à être connue par les classes d'IHM et de Scripting.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Salut,

    Si j'ai bien compris, ta classe core devrait sans doute agir comme une grosse facade pour le reste de ton application, dans le sens où elle expose un ensemble de fonctionnalités "standardisées" pour que les autres éléments (ui, script_executor et menu) puissent y profiter "facilement" d'une interface destinée à "cacher" la complexité de tes données métier.

    Il se peut que je me trompe, mais tu pourrais peut-être envisager de "scinder" cette façade en au moins deux parties:
    • une partie qui n'exposerait que les fonctionnalités propres au menu et
    • une partie qui n'exposerait que les fonctionnalités propres à l'exécution des scripts

    (étant entendu que ce qui a trait à l'ui a de bonnes chances... d'avoir besoin des deux facades pour fonctionner correctement)

    Maintenant, pour le respect de l'ISP, c'est relativement simple :

    Java (par exemple) vient avec la notion d'interface, qui cache le fait que ce sont des classes ne présentant que des fonctions virtuelles pures, et le fait que le mot clé implements occasionne un héritage de la classe qui implémente l'interface vis à vis de l'interface.

    Mais C++ ne fait pas cette distinction "artificielle" entre une classe (qui peut servir de "classe de base" dans un héritage) et une interface. Mieux encore : il n'oblige absolument pas à créer des fonctions virtuelles pures dans tes interfaces.

    Tu pourrais donc créer des classes "holder" (propriétaires ou non) pour les différentes fonctionnalités que ta (tes) facades doivent exposer, quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Class1Hoder{
    public:
        /* en C++, tu peux très bien envisager d'implémenter toutes 
         * ces fonctions, qui n'ont pas forcément besoin d'être virtuelles
         * et qui peuvent donc ne pas être virtuelles pures
         */
        void modify(/* params*/);
       Type getSome() const;
       void someClass1Functionnality(/* params */);
    private:
        Class1 data_;
    };
    qui, tu t'en rend compte à la lecture du code, ne ferait en réalité qu'une chose : exposer les différentes fonctionnalités dont tu as besoin pour manipuler un élément de type Class1 (et, accessoirement, maintenir les données de type Class1 en mémoire "aussi longtemps que nécessaire".

    Bien sur, tu ferais cela pour les données auxquelles ta (tes) facade(s) sont destinées à donner accès.

    Par la suite, la création d'une (de) facade(s) pourrait très bien se limiter à... un héritage public (et multiple) de l'ensemble des interfaces dont elles ont besoin, sous une forme proche de
    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
    class MenuFacade : public class1Holder, // Class1, Class2 et Class3 sont utilisées pour les menus
                                  public class2Holder,
                                  public class3Holder{
    public:
        /* on ne veut surtout pas autoriser la copie ou l'affectation de
         * notre classe
         */
        MenuFacade(MenuFacade const &) = delete;
        MenuFacade& operator = (MenuFacade const &) = delete;
        /* oui, ca fait très "singleton" hein ?
         * Fais juste attention à n'y avoir recours que... pour ce
         * qui a trait aux menus ;)
         */
        static MenuFacade& get(){
            static MenuFacade instance;
            return instance;
        }
    private:
        MenuFacade() = default;
        ~MenuFacade() = default;
    };
    Et, bien sur, tu pourrais faire pareil avec la facade exposant les fonctionnalités de scripts (et les interfaces correspondant aux diverses fonctionnalités qu'elle doit exposer)

    Notes bien que je parle de plusieurs facades distinctes, mais ce pourrait tout aussi bien être ta classe core, qui hériterait alors de l'ensemble des interfaces créées

    Car ta classe core (ou les différentes facades dont je parle) ne sont en définitive que... des classes "utilitaires" qui se contentent de regrouper (et d'exposer) un ensemble de fonctionnalités "qui vont bien ensemble" (car toutes dirigées vers un même objectif).

    L'avantage de travailler de la sorte est que, si tu as un problème avec une des fonctionnalités, tu pourras t'intéresser uniquement aux fonctions exposées par le holder adéquat, sans risque un "parasitage" des 250 autres fonctionnalités exposées par les autres holders.

    Comme tu le vois, on en revient toujours au SRP : une classe ne s'occupe que d'une seule et unique chose, mais veille à s'en occuper correctement
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

+ Répondre à la discussion
Cette discussion est résolue.

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