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 :

Trop de set/get dans mes classes


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 Trop de set/get dans mes classes
    Bonjour,

    J'ai un souci de conception qui me dérange de plus en plus et j'aimerai votre avis.

    Je dois maintenir un programme de taille moyenne (140 fichiers, 60 classes).

    Schématiquement, une classe "core" manage une tripotée de classes spécialisées A, B , C , D ... environ 40 classes, chaque classe spécialisée a environ 3 à 5 assesseurs.
    chaque classe spécialisée est définie ainsi dans core :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    classe core {
     
    public:
      ...
    private:
      class* A a;
      class* B b;
      class* C c;
      ...
    }
    Si je me focalise sur les get/set, j'ai donc dans core environ 120 à 200 set/get. et core ressemble à cela:

    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
    classe core {
     
    public:
     ...
     setClass_a1()
     getClass_a1()
     setClass_a2()
     getClass_a2()
     setClass_a3()
     getClass_a3()
     setClass_b1()
     getClass_b1()
     setClass_b2()
     getClass_b2()
     setClass_b3()
     getClass_b3()
     setClass_b4()
     getClass_b4()
     ...
    private:
      class* A a;
      class* B b;
    }

    car ma classe core est dirigée par une autre classe "manager" qui gère un moteur de script et qui a besoin d'accéder aux get/set des classes spécialisées. Comme il n'y a pas de lien entre "manager" et les classes spécialisées, les concepteurs ont tout fait passé par "core"

    Bref, ca commence à m'embêter sérieusement car ma classe "core" devient au final illisible.

    ensuite c'est le problème même des set/get

    j'ai donc pour une variable dans une classe spécialisée
    - un set:get pour cette variable [dans le fichier de définition de la classe spécialisée en question]
    - un set/get dans la classe core qui ne sert que de relai. [dans le fichier de définition de la classe core]
    - un set/get dans ma classe "manager" pour modifier effectivement la variable de la classe spécialisée. [dans le fichier de définition de la classe "manager"]


    Je souhaiterai changer tout cela.

    J'ai mes idées mais comme toute implication nécessite de changer énormément de choses, j'aimerai votre avis avant de tout changer pour éviter de tout rechanger à nouveau.

    Déjà merci pour votre aide !

  2. #2
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut
    Si je résume, tu as un code qui applique à la lettre la loi de Déméter :
    https://fr.wikipedia.org/wiki/Loi_de_D%C3%A9m%C3%A9ter

    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
    // A, B , C , D... environ 40 classes ayant chacune 3 à 5 paires de get/set.
     
    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;}
    };
     
    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;}
    };
     
    // core a une instance de chacune des classes ci-dessus
     
    class core
    {
    private:
        A m_a;
        B m_b;
        // ...
    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);}
        // ...
    };
     
    class manager
    {
    private:
        core& m_core;
        // ...
    public:
        manager(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);}
        // ...
    };
    Pour éviter de multiplier les get/set, on peut aussi faire un code qui ne respecte pas la loi de Déméter :
    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 core
    {
    private:
        A m_a;
        B m_b;
        // ...
    public:
        const A& get_a_ref() const {return m_a;}
              A& get_a_ref()       {return m_a;}
        const B& get_b_ref() const {return m_b;}
              B& get_b_ref()       {return m_b;}
        // ...
    };
     
    class manager
    {
    private:
        core& m_core;
        // ...
    public:
        manager(core& param) : m_core(param) {}
        const core& get_core_ref() const {return m_core;}
              core& get_core_ref()       {return m_core;}
        // ...
    };
    L'inconvénient de la loi de Déméter, c'est que l'on multiplie les fonctions.

    L'avantage, c'est qu'on a plus d'encapsulation.
    Par exemple, admettons que je fasse les changements brutaux suivants :
    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
    // La classe A disparaît.
    // core::get_a1 et core::get_b1 sont fusionnés en une seule fonction.
     
    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;}
    };
     
    class core
    {
    private:
        double m_a1;
        int    m_a2;
        bool   m_a3;
        B m_b;
        // ...
    public:
        core() : m_a1(0), m_a2(0), m_a3(false) {}
        double get_a1_ou_b1(bool a) const {return a ? m_a1 : m_b.get_b1();}
        int    get_a2() const    {return m_a2;}
        bool   get_a3() const    {return m_a3;}
        double get_b2() const    {return m_b.get_b2();}
        bool   get_b3() const    {return m_b.get_b3();}
        // ...
        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   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);}
        // ...
    };
     
    class manager
    {
    private:
        core& m_core;
        // ...
    public:
        manager(core& param) : m_core(param) {}
        double get_a1() const    {return m_core.get_a1_ou_b1(true);}
        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_a1_ou_b1(false);}
        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);}
        // ...
    };
    L'interface de manager ne change pas, donc il n'y a pas besoin de modifier le code des utilisateurs de cette classe.

    Quand faut-il appliquer la loi de Déméter ? Tout dépend du contexte.
    Il y a des avantages et des inconvénients à peser au cas par cas.

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

    Informations professionnelles :
    Activité : aucun

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

    Déjà, le simple fait que ta classe Core manipule toutes les classes sous la forme de pointeurs, c'est relativement louche! :
    • Soit ta classe Core n'est qu'un utilisateur des instances de ces classe, et il serait sans doute opportun d'envisager l'usage de référence au lieu de pointeurs (voir de std::experimental::optional, si certains éléments peuvent ne pas exister)
    • Soit ta classe Core est le propriétaire légitime des différentes instances, et, dans ce cas là:
      • Soit n'y a pas besoin de recourir au polymorphisme (ce qui semble se confirmer, vu que tu n'as de toute évidence que des get et des set), ta classe Core devrait n'utiliser que des instances "classiques" (pas d'allocation dynamique)
      • Soit tu as besoin de recourir au polymorphisme, et tu devrais envisager l'utilisation de pointeurs intelligent; selon le cas:
        • Ta classe Core est l'unique propriétaire (la seule à décider quand chaque instance peut être détruite), et elle devrait les manipuler sous la forme de std::unique_ptr ou
        • la décision de détruire une instance donnée est "collégiale" (décidée par différentes classes / instances), et ta classe Core (ainsi que les autres, d'ailleurs) devrait les manipuler sous la forme de std::shared_ptr.

    Ceci étant dit :

    La présence de mutateurs (ou setters) : tous ces setXX, setY sont le symptome évident d'un problème de conception beaucoup plus profond: le(s) développeur(s) a (ont) pensé à leur classe en terme des données qu'elles permettaient de représenter et non en termes des comportements qu'elles devaient proposer.

    Du coup, alors que l'accessibilité privée nous donne une occasion en or massif d'encapsuler correctement et de manière totalement sécurisée les données, en permettant que toute la logique de calcul des nouvelles valeurs et de modification soit totalement transparente à l'utilisateur, le(s) développeur(s) n'a (n'ont)en réalité absolument rien encapsulé DU TOUT : n'importe qui peut décider à n'importe quel moment de faire un calcul "qui lui parait juste" (mais qui peut parfaitement être totalement aberrant) afin d'utiliser le résultat obtenu pour modifier la valeur d'une des données... Au risque que cette valeur soit totalement aberante et mène au crash pur et simple de l'application.

    J'ai bien conscience de n'avoir aucune connaissance du projet, mais, si j'étais toi :
    • je remettrais déjà en cause l'existence même de ta classe Core : si c'est pour que tu finisse par donner un accès illimité à toutes les fonctions (qui ne sont, de plus que des accesseurs et des mutateurs) de tous les éléments qu'elle contient, elle n'a qu'un intérêt quasi nul
    • je profiterais que le projet est encore "à ses débuts" (avec 140 fichiers et 60 classe, cela reste *** relativement *** simple de rectifier le tir) pour
      • insiter pour revoir la conception depuis 0, car elle est foireuse en l'état
      • (faire virer le responsable qui a décidé de telles inepties, ou à défaut lui recommander la lecture de mon livre )
    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

  4. #4
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut
    @tickyletroll : Pour aller plus loin, il faudrait que l'on sache davantage ce que représentent ces classes A, B, C, ..., core et manager.
    D'ailleurs, est-ce que la classe core est ou sera utilisée par d'autres classes que manager ?

  5. #5
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Je partage avec Koala la gêne causée par la présence de tous ces getters/setters, mais je ne proposerais pas forcément de tout revoir, ça dépend...

    Il existe deux types de classes : Celles qui ont un comportement, fournissent des services, possèdent des invariants, et celles qui ne sont qu'un agrégat de données, de simples structures. Prenons par exemple une classe Personne dans deux applications différentes :
    - Dans une application de gestion des électeurs, une Personne va être composée d'un nom, prénom, date de naissance, adresse, bureau de vote, date de délivrance de la carte électorale. Et c'est à peu près tout.
    - Dans une application de type simcity, une Personne va avoir un objectif, se déplacer pour l'accomplir, posséder des modèles d'évolution de son comportement en fonction des stimulis externes (nourriture, luxe, sécurité ressentie...). Elle va aussi posséder tout un tas de données membre, mais c'est un peu secondaire, elle est tout d'abord définie par ses comportements.

    Comment ça se traduit dans le code ?

    Le cas simcity est le cas noble, celui dont parle Koala. On a une classe dont l'interface est composée de fonction rendant des services. Cette classe possède des invariants que l'interface publique assure de conserver. Toutes les données membre sont privées, et s'il existe un ou deux accesseurs, c'est plus l'exception que la règle, la plupart des données n'étant pas accessibles individuellement (par exemple, on n'accède pas en direct au niveau de satiété d'une personne, mais on peut dire qu'elle se nourrit, et on peut savoir qu'elle va manifester pour avoir plus de pain). Celle qui le sont sont souvent en lecture seule (le nom de la personne n'a aucune raison de changer, mais il peut être intéressant d'y accéder dans le reste du programme, comme un identifiant).

    Dans le cas gestion des électeurs, la classe sera presque uniquement constituée des données membres. Elles peuvent être toutes publiques, puisqu'il n'y a aucun invariant à faire respecter au niveau de cette classe (il y a des invariants intrinsèques à chaque donnée membre prise individuellement, par exemple la date de naissance doit être valide, mais c'est le rôle du type gérant cette donnée de s'en assurer). Elles peuvent contenir des constructeurs, afin d'être correctement initialisées, mais généralement, elles n'auront pas d'autres fonctions membres (éventuellement des fonctions de sérialisation). Dans une application de gestion, la représentation en mémoire de données gérées par une base de données entre assez souvent dans cette catégorie.

    J'utilise parfois le vocabulaire class/struct pour différencier ces deux cas, même si ça ne correspond pas vraiment à la distinction qu'en fait le langage. Et dans mon code, je vais utiliser class pour le premier cas, et struct pour le second, mais c'est juste une convention d'écriture qui me permet de savoir rapidement à quelle catégorie appartient un type.

    Si tu penses qu'une classe de ton application est une "class", dans ce cas là, je te conseille effectivement d'en revoir totalement la conception et son interface publique. Un bon moyen pour ça peut être de commencer à écrire du code manipulant cette classe de la manière qui semble la plus naturelle et encapsulée possible, puis d'inférer l'interface de cette classe à partir du code que tu auras écrit.
    Si tu penses qu'elle est plus un "struct", enlève les accesseurs, met tout en public, oublie Demeter, écrit du code qui n'est plus vraiment orienté objet, il n'y a pas de honte à avoir si l'application ne s'y prête pas.

    PS: Koala, je n'ai pas lu ton livre, je ne sais donc pas si nous sommes en accord ou pas sur ce point.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    PS: Koala, je n'ai pas lu ton livre, je ne sais donc pas si nous sommes en accord ou pas sur ce point.
    Tout à fait, ou peu s'en faut...

    Disons que c'est la classique distinction entre sémantique de valeur (les "agragats de données") et sémantique d'entité (les hiérarchies de classes)...

    il se peut qu'il y ait des invariant à faire respecter (pour déterminer si l'instance d'une classe ayant sémantique de valeur est "valide"), mais c'est beaucoup plus en rapport avec le concept qu'elle doit représenter qu'en rapport avec les services dont nous pourrons disposer pour manipuler sereinement le concept: le jour dans une date ne sera jamais supérieur à 31 (aux variation en fonction du mois près), et le mois ne sera jamais supérieur à 12.

    Ceci dit, l'idéal est -- selon moi -- de toujours respecter la constance des instances de classes ayant sémantique d'entité. Du moins, autant qu'il est raisonnable de le faire. Car, a priori, il reste toujours (ou presque) un fait commun : si tu modifie un tant soit peu un élément qui compose la valeur, tu obtiens littéralement une valeur totalement différente (l'exemple de la personne pour une application de gestion des électeur étant un tout petit peu particulier, car il n'y a que certains champs de la structure qui sont réellement constant )
    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

  7. #7
    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
    Bonjour !

    Déjà mille mercis pour vos réponses car vos interventions m'ont permis d'y voir bien plus clair et surtout de bien comprendre mon problème.

    Merci Pyramidev pour sa définition du code qui applique à la lettre la loi de Déméter. C'est exactement cela!

    J'ai regardé mes classes en suivant la sémentique de JolyLoic : je n'ai que des classes et pas de struct.

    Je pense qu'initialement ma classe core devait gérer l'ensemble des classes spécialisées du programme, et pour faire cela, aucun set/get n'était utile. Ce qui était cohérent avec la loi de Déméter. La classe "core" était propre et lisible.

    J'ai regardé alors ma classe "manager" d'un peu plus prêt. C'est à cause de cette classe que les get/set ont fleuris au moment ou "manager" a été implémentée, rendant core "gros", "gras" et "illisible".

    J'ai donc suivi le conseil de koala01 complété par celui de JolyLoic : en revoir totalement la conception et son interface publique.
    Je suis donc entrain de me poser la question: mais comment écrire le code de manière naturelle et encapsulée possible.


    Mon idée (qui vaut ce qu'elle vaut) serait alors la suivante:

    Dans Core, partie privée je vais regrouper toutes les classes spécialisées dans un gestionnaire noté Composant. J'autorise l'accès à ce gestionnaire avec un getComposant public.

    Pour accéder en dehors de Core à une de mes classes spécialisées, j'exécute un core->getComposant("A") pour obtenir un pointeur sur mon objet de type A et donc de modifier A comme je le souhaite. (en effet toutes mes classes spécialisées sont uniques)

    Je supprimerai tous les set/get de "core", tous les set/get de "manager" et encore quelques set/get d'autres classes d'interface sur "core", ce qui rendra mon programme plus lisible et bien plus clair.

    Merci à tous.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par tickyletroll Voir le message
    J'ai regardé mes classes en suivant la sémentique de JolyLoic : je n'ai que des classes et pas de struct.
    Ca, ce n'est clairement qu'un détail :

    A partir du moment où toutes tes classes exposent une fonction get et une fonction set pour chacune des données quelle manipule, tu aurais écrit
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    struct A{
    /* l'accessibilité par défaut de struct est publique
     * --> pas besoin de get ni de set
     */
    tes données viennent ici
    };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class A{
    /* forçons les données à se trouver dans l'accessibilité publique */
    public:
    tes données viennent ici
    /* vu qu'elles sont publique, plus besoin de get ou de set sur tes données */
    };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    struct A{
    les get et les set viennent ici (dans l'accessibilité par défaut qui est publique)
    private:
    tes données viennent ici
    };
    ou

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    struct A{
    /* on force l'accessibilité publique pour les get et les set */
    public:
    les get et les set viennent ici 
    /* et l'accessibilité privée pour les données */
    private:
    tes données viennent ici
    };
    ou même
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct A{
    /* on profite de l'accessibilité privée par défaut du mot clé class pour mettre
     * les donnée en premier lieu
     */
    les données viennent ici
    /* mais les get et les set doivent être publiques */
    public:
    les get et les set viennent ici 
    };
    cela serait revenu exactement au même (au détail près que l'on n'utilise pas forcément les get et les set dans certains cas): tous ces exemples (tout à fait légaux) sont des exemples A NE SURTOUT PAS SUIVRE pour une raison qui est toujours la même : c'est celui qui utilise la classe qui doit veiller à ce que le calcul qui permet de déterminer la nouvelle valeur d'une donnée soit juste.

    Et la loi de murphy fera qu'il se plantera systématiquement !

    Je pense qu'initialement ma classe core devait gérer l'ensemble des classes spécialisées du programme, et pour faire cela, aucun set/get n'était utile. Ce qui était cohérent avec la loi de Déméter. La classe "core" était propre et lisible.


    J'ai regardé alors ma classe "manager" d'un peu plus prêt. C'est à cause de cette classe que les get/set ont fleuris au moment ou "manager" a été implémentée, rendant core "gros", "gras" et "illisible".
    Hummm... dés que j'entend (ou que je lis) des terme comme "gérer", "manager", management", système, pour définir la responsabilité d'une classe, je suis pris de frissons et de frayeurs noctures...

    Car ces termes veulent strictement tout dire et ne rien dire, si bien que les classes qui implémentent ce genre de responsabilité finissent par ... tout faire, de manière tellement intriquée qu'il devient totalement impossible de modifier la moindre ligne de code sans risquer de "tout casser". Et ce, en contradiction flagrante du tout premier principe de programmation, celui qu'il faut respecter à tout prix : le SRP (pour Single Responsability Principle ou principe de la responsabilité unique).

    Ce principe ne signifie pas seulement qu'une fonction doit ne s'occuper que d'une seule chose, mais aussi que chaque type de donnée que tu envisages de créer ne doit être utilisé que dans un seul objectif bien déterminé.

    Le sens donné aux termes que j'ai cité est "tellement vaste" que l'on fini forcément par mélanger des objectifs qui auraient été bien plus faciles à atteindre s'ils étaient restés "seuls dans leur coin"

    J'ai donc suivi le conseil de koala01 complété par celui de JolyLoic : en revoir totalement la conception et son interface publique.
    Je suis donc entrain de me poser la question: mais comment écrire le code de manière naturelle et encapsulée possible.
    La réponse est simple : penses en termes de comportements auxquels tu veux pouvoir accéder en tant qu'utilisateur (par exemple, une fonction move(diffX, diffY, diffZ) qui calcule la nouvelle position de l'objet en fonction du déplacement qui sera effectué). Et ce n'est qu'une fois que tu sauras de quels comportements tu as besoin que tu commencera à réffléchir aux données qui permettront à ces comportement d'agir en conséquence.
    Ensuite, veille à respecter SCRUPULEUSEMENT les principes SOLID ainsi que la loi de Déméter

    S, O, L et la loi de Déméter doivent littéralement être suivis à la lettre. Si un de tes choix possibles ne les respecte pas, le choix doit être écarté.
    On peut être un peu plus souple dans le respect de I et de D si on est face à quelque chose qui a vraiment peu de chance d'évoluer par la suite. Mais chaque fois que tu y contreviendra, tu prendra le risque de perdre un temps appréciable à la prochaine évolution
    Mon idée (qui vaut ce qu'elle vaut) serait alors la suivante:

    NOTA: il est intéressant de constater que, très souvent le respect de deux ou trois de ces principes et lois occasionne très rapidement le respect de tous les autres
    Dans Core, partie privée je vais regrouper toutes les classes spécialisées dans un gestionnaire noté Composant. J'autorise l'accès à ce gestionnaire avec un getComposant public.
    PAS DE GESTIONNAIRE!!!
    Crée des propriétaires, des utilisateurs, des fabriques CLAIREMENT SEPARES et ne cede surtout pas à la tentation de regrouper ces différents objectifs au sein d'une seule et unique classe!!!

    Pour accéder en dehors de Core à une de mes classes spécialisées, j'exécute un core->getComposant("A") pour obtenir un pointeur sur mon objet de type A et donc de modifier A comme je le souhaite. (en effet toutes mes classes spécialisées sont uniques)
    Tu continues à penser en termes de données!!!

    Comme je te l'ai dit : tu dois penser en termes de comportements : l'utilisateur de ta classe ne devrait même pas savoir qu'elle utilise en interne la classe A ou la classe B (qui pourraient très bien ne pas exsiter dans l'esprit de l'utilisateur)
    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

  9. #9
    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
    Et bien.
    Merci pour ces intéressantes précisions !

    Désolé pour les frissons et les frayeurs nocturnes, j'ai repris le vocabulaire actuel des classes.
    mais je comprends.


    Bon alors avant de toucher à quoi que ce soit, je vais potasser SOLID et Déméter, et suivre tes remarques.

    Merci encore pour toutes ces précisions.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par tickyletroll Voir le message
    Désolé pour les frissons et les frayeurs nocturnes, j'ai repris le vocabulaire actuel des classes.
    mais je comprends.
    Bah, tant que tu ne me demande pas d'intervenir sur le code ou dans la conception, les frissons ne sont que passagers et tu fais absolument comme tu l'entends

    Et puis, il ne faut pas trop t'en faire : je suis connu pour avoir souvent recours à des "phrases choquantes". Cela pimente la conversation et tend à inciter les gens à se poser des questions

    Ca permet d'entretenir la discussion
    PS : je me permet de déroger à la règle de confidencialité des messages privés pour te donner une partie de la réponse, dont je souhaite que tout le monde puisse profiter...
    Citation Envoyé par tickyletroll
    Bonsoir,

    Comme dit, encore merci pour vos remarques claires et pertinentes.
    Mais de rien
    Je pense passer un peu de temps à réfléchir sur mon problème avant de modifier les choses en me documentant.
    Oui, mais ne passe quand meme pas trop de temps là dessus... autrement, tu ne recommencera jamais à coder

    Et puis, il est tout à fait possible d'y aller par étapes : En commençant par les classes les plus simples, on prend une classe bien particulière, et
    pour chaque "set" on réfléchit à:
    1. est-ce que cela correspond (quelle que soit la logique de calcul de la nouvelle valeur) à un (ou plusieurs) comportements que nous pourrions définir très précisément
    2. si oui,
      1. quels sont les invariants qui devront être respectés
      2. quelles sont les préconditions qui devront être respectées
      3. quelles sont les postconditions qui devront être respectées
    3. puis on regarde les services que nous venons d'exposer, et on vérifie qu'ils corresondent bien tous à une responsabilité bien précise,
    4. si ce n'est pas le cas, on réparti les différents services dans "autant de classes qu'il y a de responsabilités)
    5. on modifie le code "restant" afin de prendre les modifications en compte (il ne faut pas que l'application arrête de fonctionner, quand même)
    6. on prend une autre classe et on recommence en (1)
    7. une fois que toutes ces classes corrigées, on passera à celles qui les utilisent en interne. Deux solutions s'offrent alors à nous:
      1. Le comportement exposé par la classe "simple" doit être exposé "tel quel" par la classe qui l'utilise (on crées alors une fonction portant le même nom que dans la classe simple, et on y fait tout simplement appel)
      2. Certains comportements de la classe simple (parfois les même que ceux exposés par la classe qui les utilisent) sont utilisés "en interne" par des comportements que l'on est en droit d'attendre de la part de la classe "utilisatrice". On expose alors ce comportement spécifique (à la classe utilisatrice) et on s'arrange pour qu'il fasse appel aux comportements de la classe simple "en respectant la logique qui doit être appliquée".
    8. On prend une autre classe "utilisatrice", et on recommence
    Même si je ne suis pas le créateur original du programme dont j'ai la charge actuellement, je ne peux pas maudire mes prédécesseurs (je ne connais pas leurs impératifs de l'époque par exemple) et cela ne m'aiderai pas plus.
    tout l'ensemble
    Ne t'en fais pas trop, j'ai bien conscience de ce que c'est
    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

  11. #11
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Tout à fait, ou peu s'en faut...

    Disons que c'est la classique distinction entre sémantique de valeur (les "agragats de données") et sémantique d'entité (les hiérarchies de classes)...
    Je pense qu'il ne s'agit pas vraiment des mêmes concepts. Pour reprendre mon exemple, la classe Personne est une classe d'entité dans le simcity comme dans la gestion d'électeurs. Dans aucun des deux cas tu ne veux en faire de copies... sauf si vraiment c'est le seul moyen de faire gagner le parti que tu préfères aux prochaines élections, et que tu as de bonnes raisons de penser que la revue de code ne le verra pas
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  12. #12
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Loic, Koala, je commence aussi à entrevoir une sémantique pour laquelle entité ou valeur n'est pas ce qui est le plus important pour nos histoires de copiabilité: les agrégats de données qui sont totalement dépourvus d'invariants que Loic a décrit.
    Pour le coup, je suis assez opportuniste: si tous les attributs sont copiables, je laisse la copiabilité. Sinon, je la vire.


    Sinon, tickyletroll, une petite parenthèse: le but de l'encapsulation n'est pas de cacher. Son but est de protéger les invariants. Cacher est le comment on s'y prend.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

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

Discussions similaires

  1. [Débutant] Get Set de multiples variables dans une classe
    Par lupus5 dans le forum Général Dotnet
    Réponses: 2
    Dernier message: 30/05/2016, 07h41
  2. Data source utilisable dans mes classe JAVA
    Par geforce dans le forum Développement Web en Java
    Réponses: 1
    Dernier message: 12/10/2015, 11h00
  3. Jeu de sudoku : problème dans mes classes Case Placement
    Par zapmtl dans le forum Débuter avec Java
    Réponses: 15
    Dernier message: 29/07/2015, 21h06
  4. [débutant]remarques dans mes class
    Par pingoui dans le forum Langage
    Réponses: 4
    Dernier message: 17/02/2007, 20h26
  5. [DAO] Comment gérer les liens avec la bdd dans mes classes?
    Par Wormus dans le forum Autres
    Réponses: 6
    Dernier message: 22/02/2006, 16h14

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