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

Langage C++ Discussion :

Que pensez-vous de la loi de Demeter ?


Sujet :

Langage C++

  1. #81
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par Lavock Voir le message
    Hum, c'est un peu hors sujet, mais que veux tu dires par là ?
    Pour des raisons d'optimisation et de respect du LSP, généralement je considère des états. L'exemple type est le calcul d'une intersection entre une droite et une quadrique : je perd énormément de temps si je ne considère pas l'état "sphère".
    Je concède que je suis un peu jusqu'au boutiste - mais c'est principalement parce que je crois sincèrement qu'on peut, en tant qu'architecte logiciel et programmeur, se débarrasser de nos mauvaises habitudes de "passer outre certaines règles histoire d'optimiser le temps de développement ou l'application". J'ai tendance à pousser les concepts à bout, histoire de voir ce qui va en sortir. Généralement, c'est très payant dans le sens ou une architecture correcte diminue énormément le temps de développement et la taille du code à écrire (d'après mon expérience). Mais je m'éloigne effectivement du sujet...

    Sinon, cf. le post de pseudocode pour les éclaircissements que je n'ai pas pensé à apporter.

    Si je veux être plus clair : dans le paradigme OO, un objet se compose de deux choses : des propriétés et un état. Ces deux éléments sont intrinsèque à l'objet. Ensuite, l'objet offre des services, qui permettent entre autre de modifier son état en fonction de l'action demandée et des propriétés de l'objet.

    Si on sépare la notion d'état de la notion d'objet, nous ne somme plus dans le paradigme OO mais dans le paradigme procédural. L'état n'est plus une donnée intrinsèquement liée à un objet mais un ensemble de grandeurs qui sont manipulées par des procédures. La loi de Demeter n'est clairement pas applicable dans ce cas, et l'encapsulation qu'on peut faire est plus que limitée...

    Citation Envoyé par JulienDuSud Voir le message
    Tiens en parlant de Manager, pour un projet sur lequel je suis sur l'analyse, j'ai finit par décider que le Manager est la meilleure solution (un manager par type d'Entité).

    Et en découplant la gestion de durée de vie de la gestion de l'entité, tu changes quoi, concrètement ?
    Ouh la la. Plein de chose, d'après moi. Si on reprends l'exemple typique que j'ai donné plus haut (un manager == collection + contrôle de la durée de vie + factory), alors découpler ces trois fonctions me permet de:

    1) étendre le système en prévoyant de nouvelles factory, et par exemple adapter les factory aux possibilités du système sur lequel s'exécute le programme. Une ressource material peut, selons la carte graphique, récupérer un shader différent ou charger des textures différentes.

    2) étendre le système en prévoyant un contrôle de durée de vie différent - tous les programmes n'ont pas besoin du même type de contrôle. Si je prends l'exemple d'un jeu, la gestion de la durée de vie des ressources est très différents selon que toutes les données sont persistantes dans le monde ou non (exemple : une créature tuée disparait du niveau, ou reste en place jusqu'à ce que le niveau soit déchargé). Je peux aussi adapter le contrôle de durée de vie en fonction des ressources que mon "manager" est censé traiter.

    3) toute collection n'est pas nécessairement équivalente. Si pour certaines ressources je peux me satisfaire d'un lookup en O(n) (très peu d'accès aléatoire, et ces derniers ne sont pas pénalisant en terme de temps - un std::vector<> ; exemple typique : la gestion de tâche dans un scheduler), d'autres ressources peuvent avoir besoin d'un tout autre type d'accès (accès aléatoire fréquent, par exemple l'accès à une texture qui pourrait se faire via un std::map<> en C++).

    4) certaines parties de mon soft n'ont aucune raison de connaître la façon dont je contrôle la durée de vie de mes ressources ; d'autres parties ne sont pas intéressé par l'aspect stockage ; etc. En découplant les différentes parties de mon manager, je diminue les dépendances entre mes modules, ce qui me permet de simplifier et leur développement, et leur maintenance. Ce qui est un point important selon moi.

    Si les trois premiers points vous semblent très YAGNIsant, le 4ème point devrait quand même vous faire tilter un peu. Notre métier, ce n'est pas tant écrire du code que de répondre à un service ; et pour ça, on doit écrire du code qui peut évoluer et qui peut avoir besoin de corrections. Plus le code est découplé, plus on gagnera du temps à répondre à ces deux besoins, et donc plus on gagnera d'argent (si l'argent rentre en ligne de compte, bien sûr). Et tout le monde est content

    Citation Envoyé par JulienDuSud Voir le message
    Mon design se rapproche de celui de Klaim sauf qu'il diffère dans le sens où je n'ai pas de classe "World". J'ai des composants Positionable, Drawable, etc... Et quand une entité est crée, chacune de ses composants va notifier son manager respectif qu'il est crée. Lorsqu'il meurt, il va notifier son manager de ça aussi. Et c'est à peu près tout au niveau de l'entité. Au niveau du manager, il va juste "stoker" un pointeur vers ces entités dans sa liste, et appeller son protocole de routine quand il veut demander à l'entité de se mettre à jour. Parallèlement, un gestionnaire de messages inter-entités est mis en place afin que les entités puissent communiquer.

    Ce qui veut dire que l'entité va effectivement dépendre de 2 "manager". Celui qui va lui dire quand se mettre à jour, et la classe de dispatch.

    Le SRP est peut-être ici violé, mais j'ai du mal à imaginer comment on peut faire autrement dans ce cas précis. Certains aiment bien se dire que si une DrawableEntity est une Entity, et que MovableEntity est une Entity, alors, on ne devrait stocker que des Entity et créer une interface "Update()" au niveau de Entity (et pas au niveau des composants). Sauf que là, on en arrive à devoir faire des downcast pour connaître le type exact (par exemple, draw() n'existe pas chez MovableEntity ). Mais oui, la classe "World", dans ce cas-ci, aura une responsabilité: mettre à jour toutes les Entity. Mais vous en conviendrez que c'est un design erroné.
    Oui. Un design plus adapté serait de passer par le pattern visiteur. En tout état de cause, il ne faut certainement pas injecter update() et draw() dans la classe entity ; et on a tout intérêt à éviter les dynamic_cast lorsqu'on le peut (note : le visiteur acyclique de R. C. Martin peut nécessiter des dynamic_cast<>, mais le visiteur étant nécessairement couplé avec la classe qu'il visite, cela n'a pas d'influence négative en termes de design).

    Citation Envoyé par JulienDuSud Voir le message
    Alors bien sûr, on peut dissocier la classe de notification de mise à jours et la classe de gestion de la durée de vie, mais ça ne change rien au problème, vu que de toute manière, la première dépend du second (peut-être pas l'inverse, mais soit).

    Moi, je ne trouve pas que le SRP est violé. A mon avis, la gestion d'une entité et le fait de savoir si la gérer est toujours pertinent va de paire. Ca ne sert donc à rien de vouloir dissocier quelque chose de sémantiquement indissociable, tout ce qu'on gagne, c'est un déplacement de code. Tout ce qu'on perd, c'est la multiplication de classes (littéralement * 2 pour tous les manager). Vu que le manager ne peut pas exister sans son gestionnaire de vie, on écrira systématiquement 2 instructions à chaque création de manager: le gestionnaire de durée de vie, et le manager lui même. Quand on est sûr comme ça que le premier ne peut exister sans l'autre, et que le second n'a pas de service à proposer à qui que ce soit d'autre que le premier, à mon avis il est inutile de les dissocier.
    Je vais être encore un peu hors sujet mais je ne suis pas tellement d'accord avec toi - il n'y a selon moi aucun lien sémantique entre la "gestion" d'une entité et le fait de savoir si il y a encore besoin de la "gérer".

    Plus généralement, il n'y a strictement aucune raison pour que le contrôle de durée de vie d'une ressource soit uniquement lié au contrôle de la liste des ressources. Il n'y a même aucune raison pour que ce dernier soit utilisateur du premier. Dans certains cas, c'est même l'inverse : c'est le contrôle de la durée de vie qui va décider si telle ou telle ressource est encore valide (exemple : une liste de connexions TCP/IP + un vérificateur de la validité des connexions). Dans certains cas extrême, ce contrôle inversé par rapport à ta vision est même asynchrone. La même vision peut être utilisée pour tout ce qui concerne la création des ressources (donc le coté factory du manager). Si j'ai une thread de streaming qui génère les ressources, c'est à elle de connaître ma liste de ressources ; ce n'est pas à ma collection de ressources de connaître ma thread de streaming - ça n'aurait pas vraiment de sens...

    Donc sans même parler de la violation évidente du SRP (malgré ce que tu dis ; le fait que trois fonctions distinctes cohabitent dans une classe est une violation du SRP, même si tu penses sincèrement que les trois ne peuvent évoluer qu'ensemble), il y a u problème lié à l'inversion des dépendances dans l'idée même d'un manager omnipotent.

    Le fait que le nombre de classe se multiplie n'est pas un problème en soit, étant donné que (grosso-modo, aux prologues près), la somme de code reste identique. Comme tu le dit, c'est du déplacement de code, mais ce n'est pas un déplacement de code gratuit : il a du sens.

    Considérant les informations incomplète que tu m'a fourni concernant ton design, je te propose cette idée, et je te pose une question : y voit-tu des inconvénients majeurs ?



    A noter que ce design n'est certainement pas parfait (c'est une tentative de présentation de solution en quelques minutes). L'idée de base est d'avoir une classe message_dispatcher semblable à celle que j'ai présenté dans ma série d'article sur une interface C++ à l'API windows ou sur gamedev.net ici. Ainsi, les classes entity_factory et entity_remover sont aussi capable de recevoir des messages et d'en émettre. Ce design, relativement simple, respecte les principes principaux dont nous n'arrêtons pas de parler(*), et permet l'application de la loi de Demeter de manière naturelle. entity_factory/entity_remover s'enregistrent en tant que récepteurs de messages au niveau de la classe plus générique message_dispatcher. On peut ainsi avoir plusieurs factory et plusieurs remover pour gérer les cas plus complexes liés à la création/libération de ressources.

    Citation Envoyé par JulienDuSud Voir le message
    Et ici, est-ce que Demeter est-il violé ?
    Au sens stricte, oui.
    Mais je trouve que si on réfléchit bien, non.
    Même si la classe Manager va appeller des méthodes (probablement non-const) sur des types d'Entity qu'il n'a pas crée, c'est quand même son but primaire. Il a été conçu pour faire ça. On peut considérer que quelque part, les Entity sont sa responsabilité unique, et qu'elles lui appartiennent.
    Si en plus ce n'est pas au manager de créer les entités qu'il gère, on se heurte à un problème dès lors qu'il s'agit pour lui de les libérer. Dans ton design, changer la policy de destruction des ressources nécessite de modifier la classe manager ; cette modification peut avoir un impact sur le code existant et entraîner des régressions dommageable à ton produit. Ton code a donc une viscosité élevée. Dans le design que je propose, il suffit d'ajouter un message et une classe remover, ce qui n'a aucun impact sur la contrôle des ressources lui même et sur le reste du code. Impossible d'introduire une régression puisqu'on ne modifie pas le code existant, et ce dernier à une viscosité moindre.

    Ce que je note aussi, c'est que (tu le reconnais toi même, même si tu le minimise) le SRP n'est pas respecté et que du coup, tu n'arrives pas à respecter la loi de Demeter. C'est un point important - parce qu'après mure réflexion, le respect du SRP est nécessaire au respect de la loi de Demeter - sans lui l'application a loi de Demeter va mener à des choses trop étranges pour être décrites ici

    Bref, tout ça pour dire que plus on réduit le couplage entre les classes, plus il est aisé de respecter SRP et les autres principes, plus le design s'ouvre, et plus il devient naturel d'utiliser la loi de Demeter. On peut me rétorquer que ça risque de faire plein de petites classes, mais ce n'est pas un mal en soi - au contraire. Ca simplifie d'autant plus la maintenance du projet.

    Après, chacun voit midi à sa porte. Je suis conscient que l'architecture OO n'est pas une science - c'est plus un art qu'autre chose. Chaque personne a une vision qui lui est propre et une approche des problèmes qui lui est propre. Je me contente d'expliquer mon approche, qui peut ne pas convenir à tout le monde, mais qui (à mon sens) a le mérite de bien mettre en exergue les relations entre les classes et de promouvoir un design léger, fait de petites classes relativement indépendantes les unes des autres, dont certaines seront (peut être un jour) aisément réutilisables dans d'autres projets.

    ---------
    (*) a propos du respect des principes dans cet exemple...
    OCP : on peut créer autant de type d'entité que l'on veut ; il faudra bien évidemment créer les visiteurs correspondants. Si les classes entity_factory et entity_remover héritent de classes de base plus générales, alors on peut aussi étendre le design au niveau des factory et remover sans avoir à modifier l'existant. A noter que le pattern visiteur a un peu de mal avec le principe ouvert/fermé - c'est la raison pour laquelle j'ai préféré le visiteur acyclique, moins contraignant, mais nécessitant d'utiliser dynamic_cast<> (ce qui n'est pas catastrophique, car le visiteur est nécessairement fortement couplé avec l'objet qu'il visite ; cf mon article V comme Visiteur)
    LSP : pas forcément facile à voir ; toutes les entités sont traitées de la même manière, dès lors qu'on les manipule via l'interface de entity.
    SRP : les liens définis entre les classes ainsi que les fonctions de chacune des classes présentées permettent de s'assurer que ce principe est respecté.
    ISP : aucune des classes présentée ne dépend de classes ou d'interface dont elle n'ont pas l'utilité. Les interfaces sont donc clairement séparées.
    DIP : on le voit clairement - les liens se font principalement sur des abstractions. entity dépends de visitor et message_dispatcher ; entity_collection dépends de entity ; ... Les seuls liens entre classes plus concrètes sont ceux qui on du sens (entity_message_dispatcher dispatche des messages sur une collection d'entité ; pas sur une collection de grenouilles afghanes). En un sens, c'est logique : ce sont là les classes métier, tandis que les autres classes, plus abstraites, n'ont pas véritablement de notion métier qui leur serait attachée.
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  2. #82
    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
    Je n'ai pas encore lu ta dernière réponse Emmanuel, mais je vais déjà répondre à celles ci, histoire de suivre dans l'ordre.

    Citation Envoyé par Emmanuel Deloget Voir le message
    Premièrement, pas de panique. Je ne fait que dire ce que je pense d'un morceau de code que je vois, sans connaître le contexte de celui-ci et sans non plus présumer de son utilisation. Je ne sais pas si c'est un jeu, un modeleur 3D, un éditeur de monde, ou une application bancaire. A la limite, je n'ai pas à le savoir - le code que tu présente est suffisamment détaché de toute règle métier pour que je ne me pose pas la question. Je réagis donc non pas à ce que tu va faire de ce code, mais bien à la façon dont ce code est conçu.

    Deuxièmement, et histoire de bien me faire comprendre : je ne cherche pas à te mettre dans un position ou tu te sentirais obligé de défendre ton design - c'est le tiens, tu n'a pas à le défendre. J'explique juste ce que j'y vois, et les quelques problèmes qui me sautent aux yeux.

    Enfin, troisièmement : je ne prétends pas avoir la science infuse - je peux me tromper. Ceci dit, je reste convaincu que dans ce cas particulier mon argumentation (car non, ce ne sont pas des assertions) n'est pas, contrairement à ce que tu dis, si fausse que ça.
    Il n'y a pas de souci, nous sommes entre gentlemen

    En fait je ne comprends pas du tout tes arguments c'est tout, parcequ'ils semblent se baser sur des "indices" qui n'ont rien d'évident pour moi mais qui auraient pu l'être dans d'autres cas, mais on va y venir.

    Supposons que je décide d'appliquer à fond la loi de Demeter sur tes différents managers. Comme tu le dit toi même plus loin, ces managers "ce sont bien comme tu dis des gestionnaires, au sens ou ils listent ce qui existe, comprennent une factory (mais n'en sont pas) et s'assurent que les manipulations sont valides.". Ca fait pas mal de travail pour une seule classe. Si chaque comportement est correctement encapsulé, je peux parier que tu aura une petite dizaine de fonction pour chaque manager.
    Je n'ai peut être pas été clair : ces managers contiennent chacun une factory entre autre, mais ne l'exposent pas, ils l'utilisent. Ce qu'ils exposent ce sont uniquement les manipulations comme par exemple ajouter un nouvel element du type géré, récupérer une instance de ce type etc. Ce sont des conteneurs avec structure spécifique à mon cas si tu préfères. J'aurais pu utiliser les termes Container ou Helper que ça aurait été encore moins clair.
    Les managers ici ne font que maintenir la cohérence de "structure" qui est censée être représentée, de manière à ce que les éléments ne puissent pas avoir des valeurs invalides pour la logique du jeu/de l'application.
    Je ne suis pas sur qu'on puisse parler de services en réalité, mais on en revient encore a cette notion de container, mais plus haut niveau que les conteneurs de base qu'on a dans les bibliothèques standard. Ici, on a juste des listes d'elements qui ne peuvent être manipulés que de certaines façons, clairement exposées par les managers et les fonctions membres des dits elements quand il s'agit d'actions isolées sur eux.

    Je suis pas sur d'être clair, mais en gros non ils n'ont qu'un role : maintenir la cohérence pour le type donné. C'est un role totalement dépendant du contexte et qui n'a pas grand chose a voir avec la programmation. Ca serait un jeu d'echec, il y aurait simplement de quoi avoir la liste des pièces "vivantes" (qui possèdent des positions), de quoi récupérer la pièce a une position donnée et de quoi ajouter/enlever des pièces; mais surtout garantir qu'il soit impossible de faire une manipulation impossible dans le contexte du jeu d'échec, comme déplacer une pièce vers une position hors du plateau (maintenir la cohérence). Ce sont des manipulations de l'état du jeu, mais ce ne sont pas les "règles" -- dans les echecs, les "règles" se résument aux déplacement possibles selon les types de pièce et les "effets" provoqués par certains états du jeu.
    Je ne sais pas si tu vois la différence? C'est la même entre un graph et son utilisation par exemple. Ou une std::map et son utilisation. Etc.

    Donc non ce ne sont pas des classes super blindées (sauf de surcharges pratiques en fait, des racourcis disons) et elles ne font pas office de factories etc, mais utilisent en interne d'autres classes qui ont chacune un role précis. Abstraction propre au concept manipulé.


    Ensuite, on continue d'appliquer cette encapsulation à ta classe World (rappel : pas d'accès aux managers depuis l'extérieur). Pour chaque manager, tu as une petite dizaine de fonction. Puisqu'il y a plusieurs manager, tu te retrouve avec plusieurs dizaines de fonctions dans ta classe.

    La conclusion s'impose d'elle même : lorsque tu fait le choix de transformer la classe World pour fournir les services qui sont à l'heure actuelle fournis par les managers qui sont stockés dans la classe World, le nombre de méthodes de World explose, ce qui est un signe assez visible que World se comporte comme une god class (ce qui ne veut pas dire que la classe fait tout, mais qu'elle est un point d'accès central à beaucoup trop de chose). Bien évidemment, puisque tu te permet de passer outre l'encapsulation (tu n'encapsule même pas les données, alors pour les comportements, on est encore loin du compte), tu te caches cet état de fait. Il n'en reste pas moins que ça reste vrai.
    On est tout a fait d'accord que si j'appliquais Demeter au niveau de World ça serait pas très judicieux et que là clairement il y a un truc qui semble clocher. C'est exactement ce que je disais dans mon premier message en fait.
    En revanche, on arrive au fait que World reste une forme de container spécialisé pour maintenir une cohérence des données, avec juste une séparation des interfaces par type de donnée que contiens ce container.
    Or Demeter justement ne peut s'appliquer sur un conteneur, et c'est bien logique puisqu'il ne fait que "porter " les données, d'une manière ou d'une autre. Le fait qu'il faille passer par des objets membres me permet justement de séparer les différents types contenus d emanières explicite parcequ'en interne les objects contenus sont organisés de manières relatives à leurs types et radicalement différentes même si liées. Bon je parle de manière très abstraite...

    En revanche ce que je ne comprends mais alors pas du tout dans ce que tu dis, c'est le rapport à une classe "god like". Peut être que notre définition ce ce terme est différent. Estce que pour toi ce type:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef map< std::pair< int, int > , ChessPiece > ChessBoardState;
    est une class "god-like"?

    Pour moi une classe "god-like" :
    1) est accesible partout
    2) peut "tout" faire

    Si World était un singleton, j'aurais dit oui à 1), mais là je ne vois pas le rapport. World ne fait que contenir des valeurs qui sont donc l'état de cette instance de World. Je peux avoir un nombre indéfini d'instances de World sans que ça pose le moindre probleme puisque ce sont des conteneurs. Le point important je pense ici est tout bêtement que le fait que ce soit un conteneur n'est pas intuitif. D'où mon premier message.
    Concernant 2), comme je disais World (via ses différents managers) ne fait que maintenir une organisation particulière de ce qu'elle contient. Autrement dit, elle ne fait "que" ça.

    Par contre il aurait été facile mais problématique sur le long terme d'ajouter les "règles du jeu" comme mécanisme interne de World. Ici j'ai volontairement isolé exclusivement World de manière a ce que différentes règles indépendances puissent "manipuler" world comme on manipule une liste, une map etc.

    Question : si seules deux classes accèdent à World, pourquoi World est-elle aussi fournie en managers ? Dans ma vision peut être un peu étriquée de l'architecture logicielle, si deux classes A et B dépendent d'une classe C, alors le nombre de services offerts par C est relativement limité. Ce n'est pas tellement le cas dans ton design, donc je me demande si ces deux classes dont tu parles ne font pas beaucoup trop de choses... Mais bon, sans les voir, je ne peux pas juger.
    Hmm en fait peut être que ça serait plsu clair si je te disais que World est en fait :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    struct World
    {
        std::list< Machin > machin_manager;
        std::map< String, Bidule > bidule_manager;
        boost::graph< Truc > truc_graph;
    };
    C'est plus ou moins le même genre de logique. En fait, tout ce qui est contenu dans les conteneurs (les managers ou un autre nom plus explicite si tu en connais un) d'un World sont "liés" parce qu'ils ont des identifiants qui font référence à d'autres objets contenus dans la même instance de World. Ce simple fait permet de savoir que tout ce qui est dans une instance de World ne peut faire référence qu'a autre chose contenu dans cette instance.

    Les classes qui manipulent World sont par exemple une qui parcours une instance de World pour sérializer, ou deserialise en peuplant le dit World. Une autre applique certaines règles "au cours du temps" et donc va changer le contenu de World, manipuler son état au cours du temps. Une autre encore va seulement récupérer les events qui sortent de World et les transmettre sur le réseau. (Je parlais de deux "types" de classes qui manipulent une instance de World : celles qui l'observent, celles qui la manipulent). Il peut y en avoir plus, je n'aurais rien a changer a World. C'est le point le plus important je pense.
    (D'ailleurs, il n'y aura pas plus de classes manipulant World. Les infos transmises par les events sont interprétées totalement différemment par les classes extérieures, par exemple dans le cas d'une "vue")


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    C'est très bien - et cela repose encore la question de "pourquoi tant de services dans World, si on n'accède pas à ces services à partir de World ?
    Le terme "service" n'est peut être pas clair. Si tu comptes un service pour une fonction d'une classe, alors effectivement ça peut faire beaucoup. Si tu comptes un service par un role impliquant différentes fonctions (souvent symétriques) alors ici World ne fait comme je disais au dessus que maintenanir de la cohérence dans les données qu'elle contient, et rien d'autre. Ah si, elle se charge aussi de fabriquer les éléments qu'elle contient, histoire d'avoir tout fermé. Personnellement, cela ne me gène pas lorsque c'est justifié d'avoir un conteneur qui utilise une factory interne, surtout quand il sagit de concepts très haut niveau et qui n'ont de sens que dans le domaine ou tu les utilises.
    Donc ici on a deux "services" assurés par les fonctions evidentes liées, qui sont semblables a celles d'un conteneur qui créerait lui même les éléments qu'il contient.


    Un état, en programmation objet, ça n'existe pas. Un état est forcément intrinsèque à un objet. Un état qui n'est pas intrinsèque à un objet signifie probablement qu'il n'a pas à être là, ou qu'il manque une abstraction quelque part.
    J'avoue que je ne vois pas la différence entre les valeurs que contiens un objet et son état. C'est en ce sens qu'un instance de World n'est qu'un état particulier, tout comme une liste de pièces d'échec décrit par les valeurs qu'elle contient l'état d'une partie en cours. Rien de plus, rien de moins.


    De plus, si c'était effectivement un état, comment se fait-il qu'il fournisse des services, par le biais des différents managers ? Un état ne fournit pas de services - un objet, qui possède un état et a pour mission de le maintenir, le fait.
    Visiblement c'est un problème de termes dans ce cas. A part maintenir un "état" cohérent d'une partie de jeu, World ne fait rien d'autre. Le terme manager est trompeur c'est certain, mais pour l'instant je n'ai rien trouvé de mieu (et j'ai longtemps cherché parceque j'hévite manager, mais les différents roles sont déjç pris par les membres des dits managers ).

    Si tu reprends le type ChessBoardState que j'ai écrit au dessus, tu vois vite ses limitations : il est possible d'avoir des données impossible pour un jeu d'echec dedans (une pièce à une position hors du plateau ou encore plus de pièces que de positions sur le plateau, etc).
    Ce qui fait qu'il vaudrait mieu l'encapsuler dans une classe qui n'expose que les opérations possibles et garanti que l'état du jeu serait "possible" dans les règles du jeu.
    C'est ce que j'ai fait avec World -- sauf que c'est pas un jeu d'echec et qu'il y a différents types d'entités interdépendantes structurées d'une manière propre au jeu et qui est malheureusement super abstraite et non intuitive a expliquer

  3. #83
    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
    Ouh la la. Plein de chose, d'après moi. Si on reprends l'exemple typique que j'ai donné plus haut (un manager == collection + contrôle de la durée de vie + factory), alors découpler ces trois fonctions me permet de:

    1) étendre le système en prévoyant de nouvelles factory, et par exemple adapter les factory aux possibilités du système sur lequel s'exécute le programme. Une ressource material peut, selons la carte graphique, récupérer un shader différent ou charger des textures différentes.

    2) étendre le système en prévoyant un contrôle de durée de vie différent - tous les programmes n'ont pas besoin du même type de contrôle. Si je prends l'exemple d'un jeu, la gestion de la durée de vie des ressources est très différents selon que toutes les données sont persistantes dans le monde ou non (exemple : une créature tuée disparait du niveau, ou reste en place jusqu'à ce que le niveau soit déchargé). Je peux aussi adapter le contrôle de durée de vie en fonction des ressources que mon "manager" est censé traiter.

    3) toute collection n'est pas nécessairement équivalente. Si pour certaines ressources je peux me satisfaire d'un lookup en O(n) (très peu d'accès aléatoire, et ces derniers ne sont pas pénalisant en terme de temps - un std::vector<> ; exemple typique : la gestion de tâche dans un scheduler), d'autres ressources peuvent avoir besoin d'un tout autre type d'accès (accès aléatoire fréquent, par exemple l'accès à une texture qui pourrait se faire via un std::map<> en C++).

    4) certaines parties de mon soft n'ont aucune raison de connaître la façon dont je contrôle la durée de vie de mes ressources ; d'autres parties ne sont pas intéressé par l'aspect stockage ; etc. En découplant les différentes parties de mon manager, je diminue les dépendances entre mes modules, ce qui me permet de simplifier et leur développement, et leur maintenance. Ce qui est un point important selon moi.

    Si les trois premiers points vous semblent très YAGNIsant, le 4ème point devrait quand même vous faire tilter un peu. Notre métier, ce n'est pas tant écrire du code que de répondre à un service ; et pour ça, on doit écrire du code qui peut évoluer et qui peut avoir besoin de corrections. Plus le code est découplé, plus on gagnera du temps à répondre à ces deux besoins, et donc plus on gagnera d'argent (si l'argent rentre en ligne de compte, bien sûr). Et tout le monde est content
    (...et le reste globalement).

    Tout à fait juste.

    Il y a juste deux choses qui me "chagrinent" dans ce que tu dis : la première c'est que j'ai l'impression (peut être fausse) que tu oublies que les abstractions sont construites avec des abstractions.
    Je passe a la deuxième qui explique la première :
    ce n'est pas parcequ'un 'manager" manipule une factory en interne qu'il porte aussi le role de factory. Selon comment on définis "manager", son role peut être aussi de simplifier, ou "garantir la bonne manipulation" de des différents roles, via une interface plus simple, plus haut niveau.

    Ce que je veux dire par là, c'est que dans absolument tous les cas, mieu vaut séparer les roles, avoir une classe factory, une classe qui gère (d'une manière propre au contexte voulu), une collection.

    Mais au niveau d'abstraction supérieur, dans certains cas, tu ne veux QUE la "gestion" et laisser les deux autres roles transparents.

    Tu arrives donc a quelque chose comme ça :
    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
     
    // on a bien séparé les roles...
    class TrucFactory;
    class TrucRegister;
    class TrucOrganizer;
     
    class TrucManager
    {
    public:
       //...j'y viens
     
    private:
     
        TrucFactory m_factory; 
        TrucRegister m_register;
        TrucOrganizer m_organizer;
     
    };
    Effectivement la plupart du temps on aurait ces elements séparés(pas dans la même instance), mais il arrive qu'on ait besoin de les avoir ensemble parcequ'ils représentent des abstractions dans une abstraction de plus haut niveau.
    Ici, on veut que l'utilisateur manipule des Truc à la fois sans avoir à s'occuper de la façon dont ils sont créés, sans avoir a les organiser lui même et sans avoir a maintenir une liste de ce qui existe lui même.
    Donc, on va exposer des fontions/services "minimalistes" qui ne demandent que le minimum d'informations et vont faire la totalité du boulot dérrière, s'occuper de la vie, l'organisation et le listing des instances de Truc. Quand on crée un Truc (via une fonction create() par exemple), ce Truc est automatiquement alloué via l'utilisation de m_factory, enregistré dans le m_register et "placé" selon les règles de structure qu'impose m_organizer. Tout cela de manière transparente, parceque c'est le role de TrucManager. (on pourrait tout a fait utiliser les autres classes séparément). C'est pour ça que je parlais au départ de "systèmes" et que Demeter s'applique aux systèmes et non juste "aux classes". Les classes, fonctions, bibliothèques sont des détails d'implémentation.

    On peut changer le code de chacune de ces classes avec au mieu aucun changement dans cette classe ci (TestManager), et au pire des changement d'interface mineurs (si les dites classes sont bien pensées, évidemment).

    Autrement dit, avoir les différents roles rassemblés dans un role de plus haut niveau ne brise pas (à mon sens du moins) Demeter, au contraire.
    Ce qui serait mauvais par contre, serait d'exposer directement les fonctions de la factory, register et organizer, sans aucune abstraction autour. Et là alors oui on serait dans un cas problématique et totalement hors de ce que suggère Démeter.

  4. #84
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Points : 4 551
    Points
    4 551
    Par défaut
    (Je m'éloigne encore davantage du sujet ; mais en fait, tout étant lié, cette conversation permettra de poser d'autres bases pour la discussion en cours).

    Je commence à comprendre ou tu veux en venir (en fait non ; je crois que j'avais compris avant, mais là, je pense être assez sûr de ce que j'ai compris).

    (note : je donne des noms abstraits aux classes ; ça va me resservir plus loin dans le post, et peut être dans les suivants).

    En gros, si j'essaie de résumer/simplifier, ton point de vue ressemble à celui-ci : à partir du moment ou une classe A n'expose pas les services (1) des classes B1...Bn à partir de laquelle elle est construite alors ces services sont des détails d'implémentation - et il n'ont pas à être considéré lorsqu'on évalue le design qualitativement, notamment par rapport aux respect de certains principes de design OO.

    Un exemple classique de manager qui obéit (vaguement ?) à ce point de vue (je pense que pas mal de monde devraient reconnaitre vaguement quelques unes des idées sous-jacentes). Ce manager semble se comporter comme un conteneur, mais ce n'est pas vraiment le cas (4).

    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
     
    class texture_manager
    {
    private:
      texture_loader m_loader;
      texture_collection m_tex;
      texture_killer m_killer;
    private:
      texture* load(const std::string& name)
      {
        texture *t = m_loader.read_from_file(name);
        m_tex.add(name, t);
        return t;
      }
      void kill(texture* t)
      {
        m_tex.remove(t->name());
        m_killer.kill(t);
      }
    public:
      texture* acquire(const std::string& name)
      {
        texture* t = m_tex.lookup(name);
        if (!t) t = load(name);
        t->inc_ref();
        return t;
      }
      void release(texture *t)
      {
        if (!t->dec_ref())
        {
           kill(t);
        }
      }
    };
    Je peux comprendre ce point de vue, mais je dois quand même dire que d'une part ce n'est pas le mien, et d'autre part je pense qu'il s'agit d'une erreur,.

    Un premier argument qui me vient à l'esprit (attention : la formulation n'est pas très heureuse ; je n'arrive pas à dormir, mais ça ne veut pas dire que je suis bien réveillé ) : le fait que ces détails soient cachés ne signifie pas qu'ils ne peuvent pas changer. Comme tu le fait remarquer toi-même, si on pense bien son interface, alors il y a peu de chance que les changements dans l'interface des B1...Bn aient un impact sur A. C'est vrai dès lors que les B1...Bn sont des abstractions bien conçues, et non pas des classes concrètes. Mais dans notre cas, les B1...Bn sont privées dans A : elle ne peuvent donc pas être une abstraction ; plus exactement, B1...Bn sont nécessairement utilisées comme si elles étaient des classes concrètes. Il y a des chances importantes que leurs interfaces changent en réponse à des changements internes. Donc A risque d'être modifié par un changement dans les B1...Bn. En conclusion, même si les B1...Bn ne sont pas accessibles depuis les clients de A, il n'en reste pas moins que ces B1...Bn sont n responsabilités de A (n > 1). Le SRP est donc violé. Je ne sais pas si je suis bien clair là.

    Si je reprends mon exemple précédent, un simple changement dans la stratégie de création de la texture (par exemple : lecture à partir d'un emplacement mémoire ; ou lecture asynchrone pour implémenter du streaming) va avoir un impact sur le manager - tout simplement parce que je ne peux pas construire une abstraction suffisamment forte pour le loader, car il est instancié et utilisé directement, donc comme une classe concrète. Même si tu changes le type de l'objet pour (par exemple) utiliser une abstraction plus ...euh... abstraite, il n'en reste pas moins que le loader étant privé, le manager sera forcément lié à une classe concrète ne serait-ce qu'au moment de l'instanciation.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    class texture_manager
    {
    private:
      texture_loading_policy *m_loader;
    public:
      texture_manager()
      {
        m_loader = new some_concrete_loading_policy();
      }
    };
    Tu ne construit donc pas une abstraction sur des abstractions comme tu semble le penser, mais une abstraction sur des objets concrets - ce qui est une violation du principe d'inversion de dépendance.

    Je comprends tout à fait que ça ne soit pas ton point de vue. Je tiens quand même à te rassurer : si j'insiste autant, ce n'est pas pour te faire changer ton code. C'est aussi pour donner à tous les lecteurs (qui doivent avoir mal aux yeux vu la longueur des posts) l'occasion de réfléchir à l'envers sur des points qui sont bien souvent considérés comme étant acquis (ne serait-ce que le terme "bidule_manager" qui, après des années de surf sur des forums de programmation, commence à me piquer les yeux sérieusement).

    Ceci étant dit, tu soulignes avec raison un point important : pour l'utilisateur, ce qui compte, c'est principalement la simplicité d'utilisation (2). Le design de manager que tu propose est, effectivement, simple. Je ne vais même pas chercher à contredire ce point tant il est évident. Durant mes K derniers posts (K >= 2, visiblement), je me suis attaché à démontrer que cette simplicité apparente venait avec un certain nombre de problèmes liés au respect des presque-sacro-saint principes de design OO, et qu'il existait une manière peut-être un poil moins intuitive mais de mon point de vue beaucoup plus efficace de répondre au même problème tout en évitant de passer outre ces principes (3).

    Concernant la définition d'une god class : ce n'est pas une classe accessible partout et qui fait tout. C'est une classe qui fait beaucoup trop de choses (pas tout ; juste "beaucoup trop") et qui, au final, fini par être utilisée trop souvent, rendant le design fragile (une modification dans cette classe peut entraîner des régressions à des endroits complètement aléatoires) et visqueux (il devient plus facile de hacker le système plutôt que d'implémenter correctement une solution à un problème). Si je me suis focalisé sur ta classe World, ce n'est pas pour te stigmatiser, mais pour la stigmatiser elle. Le fait qu'elle servent de point d'entrée à de nombreux modules est un signe (voire une mauvaise odeur) qui tend à montrer qu'il s'agit d'une god class. Maintenant, tu dis aussi que cette classe est très peu utilisée, ce qui m'amène à me poser la question "mais pourquoi contient-elle tout ça, si presque personne ne l'utilise ?". De deux choses l'une : soit les classes qui l'utilisent servent en fait d'indirection pour utiliser World (et dans ce cas, le problème est similaire) ; soit les classes qui ont besoin des éléments constitutifs de World les récupèrent avant de les utiliser (et dans ce cas, World n'est pas franchement d'une grande utilité).

    Il est fort possible, vu la structure de World tel que tu l'as décrite, qu'on soit dans le second cas - ce qui expliquerait que tu persiste à considérer World comme une espèce de "conteneur d'un tas de choses". Il conviendrait de se demander si c'est vraiment utile, et si il n'y a pas moyen de supprimer cette classe en redéfinissant de manière correcte les liens de propriété entre les différents objets.

    Je me trompe peut-être : après tout, il est tout à fait possible de concevoir un design ou une sorte de base de donnée est au centre du programme. C'est peut être aussi comme ça que tu conçoit ta classe World (et si c'est le cas, j'avoue ne pas l'avoir compris à la lecture de tes posts ; je reste cependant sur mon idée première, car d'après tes explications, World ne ressemble pas vraiment à une base de donnée dans le sens ou son utilisation n'est pas restreinte au stockage et à la recherche de données).

    --
    (1) un service offert par une classe est une méthode de cette classe. J'utilise ces deux noms de manière interchangeable dans ce contexte.
    (2) dès lors que faire des choses simple lui suffit. Mais les utilisateurs sont de drôles de petits monstres. Ils aiment faire des choses qui ne sont pas prévues par le fournisseur...
    (3) en fait, pour être tout à fait franc et honnête, j'ai commencé par réagir au mot "manager". Le pire, c'est qu'il me semble que c'est moi qui l'ai jeté le premier. Pas le courage de vérifier...
    (4) on n'est pas vraiment dans l'ordre, je sais. Je voulais ramener ça au design de la librairie standard C++, et notamment aux conteneurs qui y sont définis. Ceux ci sont typiquement composé d'un allocateur et d'une collection. Le type de l'allocateur est paramétrable grâce à l'interface proposée. Ce n'est pas pour rien: sans cette possibilité, les conteneurs sont limités à un seul type de gestion de la mémoire, ce qui est un peu court. Un manager qui possède une factory impossible à changer est très similaire à un conteneur de la SCL dont on ne pourrait pas choisir l'allocateur. Son utilisation est limitée à ce qu'a prévu le programmeur.
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  5. #85
    Membre régulier

    Profil pro
    Inscrit en
    Mars 2008
    Messages
    41
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2008
    Messages : 41
    Points : 99
    Points
    99
    Par défaut exemple de non respect justifié de la loi de demeter
    bonjour,

    j'ai un exemple pour lequel briser la loi de demeter me semble justifié.
    Imaginons un objet avec une centaine d'attributs (on le fait persister dans une table unique de BDD relationnelle) ; on peut très bien vouloir éclater cet objet en plusieurs sous objets pour avoir un classement plus clair de l'information ; du coup quand on va chercher l'information la plus fine on brise la loi de demeter.
    ex :
    bulletinRecensement.caracteristiquesLogement.adresse.rue
    bulletinRecensement.caracteristiquesLogement.adresse.commune

    bulletinRecensement.caracteristiquesOccupantprincipal.informationsEmploi.typeEmploi.
    bulletinRecensement.caracteristiquesOccupantprincipal.informationsScolarite.niveauEtudeAtteind.

    (ou idem avec les getters qui vont bien).

    Qu'en pensez vous?

    Cordialement
    loïc midy

  6. #86
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par loicmidy Voir le message
    bonjour,

    j'ai un exemple pour lequel briser la loi de demeter me semble justifié.
    Imaginons un objet avec une centaine d'attributs (on le fait persister dans une table unique de BDD relationnelle) ; on peut très bien vouloir éclater cet objet en plusieurs sous objets pour avoir un classement plus clair de l'information ; du coup quand on va chercher l'information la plus fine on brise la loi de demeter.
    ex :
    bulletinRecensement.caracteristiquesLogement.adresse.rue
    bulletinRecensement.caracteristiquesLogement.adresse.commune

    bulletinRecensement.caracteristiquesOccupantprincipal.informationsEmploi.typeEmploi.
    bulletinRecensement.caracteristiquesOccupantprincipal.informationsScolarite.niveauEtudeAtteind.

    (ou idem avec les getters qui vont bien).

    Qu'en pensez vous?

    Cordialement
    loïc midy
    Deux problèmes :

    1) une centaine d'attributs dans une table unique d'une BDD, c'est cruel. Bien évidemment, il doit y avoir des cas dans lequel c'est vaguement justifié. Je ne suis pas expert dans le domaine des BDD, donc je ne me permettrais pas de juger, mais je trouve quand même que ça ne sens pas très bon.

    2) le layer métier dans le programme n'a pas besoin d'être une simple représentation de la BDD. Ce n'est pas aprce que ma BDD contient les tables (ou les requêtes) A, B, ...Z que ma couche d'abstraction doit avoir les classes Ca, Cb, ... Cz avec un mapping 1-to-1 vers la BDD. Un tel mapping n'isole pas les changements de la BDD du reste du code, et peut provoquer bien des problèmes de maintenance (que j'ai vécu sur un projet intranet récent ; c'est extrêmement pénible de revoir des dizaines et des dizaines de fichiers parce qu'on a rajouté ou supprimé une colonne dans une table).

    Cependant, on touche un point que j'ai soulevé dans mon post précédent - le problème des bases de données. Je n'ai pas véritablement réfléchi à cette question. Le cas du recensement est un cas intéressant : des centaines de réponses sont stockées dans la base de donnée pour chaque individu. Certaines tables doivent, effectivement, être de taille importante (quoi que ; les données doivent pouvoir être organisées de manière assez intelligente pour justement éviter des tables avec trop de colonnes). J'ai un peu de mal à imaginer l'impact que ça peut avoir sur le design d'une application ayant pour but de traiter ces données.

    Si tu as des informations à partager, je suis toute ouïe
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  7. #87
    Rédacteur
    Avatar de pseudocode
    Homme Profil pro
    Architecte système
    Inscrit en
    Décembre 2006
    Messages
    10 062
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 51
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Architecte système
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2006
    Messages : 10 062
    Points : 16 081
    Points
    16 081
    Par défaut
    C'est moi ou ce que vous décrivez tous comme un "manager" est en fait un pattern DAO ?
    ALGORITHME (n.m.): Méthode complexe de résolution d'un problème simple.

  8. #88
    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
    Concernant la définition d'une god class : ce n'est pas une classe accessible partout et qui fait tout. C'est une classe qui fait beaucoup trop de choses (pas tout ; juste "beaucoup trop") et qui, au final, fini par être utilisée trop souvent, rendant le design fragile (une modification dans cette classe peut entraîner des régressions à des endroits complètement aléatoires) et visqueux (il devient plus facile de hacker le système plutôt que d'implémenter correctement une solution à un problème). Si je me suis focalisé sur ta classe World, ce n'est pas pour te stigmatiser, mais pour la stigmatiser elle. Le fait qu'elle servent de point d'entrée à de nombreux modules est un signe (voire une mauvaise odeur) qui tend à montrer qu'il s'agit d'une god class. Maintenant, tu dis aussi que cette classe est très peu utilisée, ce qui m'amène à me poser la question "mais pourquoi contient-elle tout ça, si presque personne ne l'utilise ?". De deux choses l'une : soit les classes qui l'utilisent servent en fait d'indirection pour utiliser World (et dans ce cas, le problème est similaire) ; soit les classes qui ont besoin des éléments constitutifs de World les récupèrent avant de les utiliser (et dans ce cas, World n'est pas franchement d'une grande utilité).

    Il est fort possible, vu la structure de World tel que tu l'as décrite, qu'on soit dans le second cas - ce qui expliquerait que tu persiste à considérer World comme une espèce de "conteneur d'un tas de choses". Il conviendrait de se demander si c'est vraiment utile, et si il n'y a pas moyen de supprimer cette classe en redéfinissant de manière correcte les liens de propriété entre les différents objets.
    En fait je crois qu'on s'est mal expliqué, puisque tu dis ensuite :
    Je me trompe peut-être : après tout, il est tout à fait possible de concevoir un design ou une sorte de base de donnée est au centre du programme. C'est peut être aussi comme ça que tu conçoit ta classe World (et si c'est le cas, j'avoue ne pas l'avoir compris à la lecture de tes posts ; je reste cependant sur mon idée première, car d'après tes explications, World ne ressemble pas vraiment à une base de donnée dans le sens ou son utilisation n'est pas restreinte au stockage et à la recherche de données).
    Si si, c'est exactement ça, une sorte de base de donnée si tu veux. J'y ai pensé hier soir mais j'étais tellement aussi fatigué que j'ai zappé.
    Donc, son role a World (qui n'est pas appelé comme ça dans mon cas mais peu importe) est bien de maintenir des infos comme dans une base de donnée qui aurait une structure particulière pour maintenir une organisation particulière et effectu des vérifications a chaque manipulation pour maintenir cette organisation particulière.
    Pour tout dire, les objets contenus dans World peuvent être manipulés indépendemment d'un World. World fournis juste un context "les liant" et de quoi vérifier que tout est "en règle" (sachant qu'une partie de ces vérifications est simplement faite par les fonctions membres des dits objets contenus).

    Donc l'idée de la base de donnée est exacte en fait. Ca devient plus clair tout a coup.

    Dans ce que tu disais avant, je vois qu'effectivement il n'y a que le cas de la base de donnée qui soit valide pour ce que tu appelles une god-class. Dans ce cas oui World est une "god class", après tout c'est la partie du programme qui représente réellement les informations qu'on manipule (en jouant ou en editant le jeu dans mon cas). Tout tourne autour et il est donc naturel qu'un changement de l'interface World impact pas mal de code (au minimum celles que j'ai cité qui manipulent directement World).

    Cela dit concernant ton exemple, je n'ai pas tout capté. J'ai bien compris cette phrase mais je suis pas sure de ce que tu commentes, mon code ou ton exemple?


    Tu ne construit donc pas une abstraction sur des abstractions comme tu semble le penser, mais une abstraction sur des objets concrets - ce qui est une violation du principe d'inversion de dépendance.
    Ca me semble juste a priori, mais ça n'implique pas (a priori dans mon cas au moins) qu'il y ait une dépendance entre l'interface d'un de mes managers et d'un de ses composants. A vrai dire j'ai changé plusieurs fois les composants internes de mes managers sans changer leur interface. Je vois comment on peut arriver au genre de probleme que tu sembles expliquer mais je n'ai pas du tout compris comment tu passes de ton premier bout de code au second (?)

    Il se peut que je ne dorme pas assez non plus ces temps ci.

  9. #89
    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 pseudocode Voir le message
    C'est moi ou ce que vous décrivez tous comme un "manager" est en fait un pattern DAO ?
    Si je compare les exemples d'ici http://www.codefutures.com/products/firestorm/benefits/

    a ce que j'ai dans mon code, ça y ressemble fortement (sans les update() ).

  10. #90
    Membre éprouvé
    Avatar de NiamorH
    Inscrit en
    Juin 2002
    Messages
    1 309
    Détails du profil
    Informations forums :
    Inscription : Juin 2002
    Messages : 1 309
    Points : 1 051
    Points
    1 051
    Par défaut
    Un smart pointer brise-t-il l'encapsulation et la loi de Demeter en disposant d'une méthode getter ? Un tableau ?

    C'est con comme question, mais une surcharge d'opérateur est une méthode comme une autre après tout...

  11. #91
    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
    Salut,
    Citation Envoyé par NiamorH Voir le message
    Un smart pointer brise-t-il l'encapsulation et la loi de Demeter en disposant d'une méthode getter ? Un tableau ?

    C'est con comme question, mais une surcharge d'opérateur est une méthode comme une autre après tout...
    Réponse dans le premier post qui lance la discussion :
    Citation Envoyé par 3DArchi Voir le message
    Bonjour,
    Ceci fait suite à cette discussion .
    On peut trouver une définition assez complète de la loi de Demeter dans ce billet d'Emmanuel Deloget.
    En résumant, l'idée de la loi est de dire que dans une méthode m d'une classe A, je ne peux utiliser que :
    -> les méthodes de A (et ses classes de bases) ;
    -> les méthodes des membres de A ;
    -> les méthodes des paramètres de m ;
    -> les méthodes des objets créés par m ;
    -> les méthodes des variables globales.
    En revanche, cette loi sous-tend qu'on n'a pas de transition : on ne devrait pas utiliser :
    -> les méthodes des membres (ou des objets retournés par) des membres de A ;
    -> les méthodes des membres (ou des objets retournés par) les paramètres de m.
    etc.
    L'idée est qu'à travers cette loi, un objet masque à son utilisateur son contenu.
    Comme le note le billet, cette règle souffre d'exception : les structures triviales, les conteneurs et toutes sortes de fabriques pour des raisons évidentes.

  12. #92
    Futur Membre du Club
    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    5
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 5
    Points : 5
    Points
    5
    Par défaut Je n'arrive pas à m'y faire
    Un accesseur (getter) ou un mutateur (setter) ne donne aucune information sur le service que tu peux attendre de ta classe ni sur le comportement que l'on peut en attendre.

    Ils se contentent, pour l'accesseur, de dire "voilà une des données utilisée" et pour le mutateur "voilà une donnée que tu peux modifier".

    Or tout le paradigme orienté objet est basé sur le fait que l'on doit penser non pas en termes de données, mais bien en termes de comportements, qu'il s'agisse de services que peut rendre une classe ou de messages auxquels la classe peut réagir (de manière interne ou en renvoyant une réponse).

    Ainsi, tout le monde sait parfaitement qu'une voiture dispose d'un réservoir à carburant.

    Mais, à moins d'être mécanicien, le réservoir en tant que tel ne nous intéresse absolument pas: ce qui nous intéresse, c'est l'interface que l'on en a dans la voiture, au travers de la jauge (pour pouvoir "interroger" la voiture sur l'état du réservoir) et de l'orifice de remplicage (pour pouvoir agir sur la quantité de carburant contenue).

    Il ne servirait à rien de demander à la voiture de nous donner accès au réservoir, et encore moins de vouloir le définir: on ne change que très rarement le réservoir sur une voiture (même si je l'ai fait sur la mienne il y a quelques mois à peine )
    J'utilise abondamment l'aggrégation dans mes développements.
    Que se passe-t-il si je développe une IHM permettant de créer / modifier une voiture ?

    Comment je fais sans setter pour changer le type de pneu ?

    Pour chaque élément modifiable M de chaque élément ELT de la voiture (par exemple couleur des sièges, couleur, type, matière de la carrosserie ...) il y aura M x ELT (réducteur) méthodes à ajouter à ma classe voiture.

    1 - Le code de la classe va être énorme (n méthodes pour chaque membres (modifieLargeurPneu() modifieCouleurPneu(), ...)
    2 - Pire encore, la classe voiture dépend complètement de chaque classe aggregée !!! (Et la, ça ne passe pas pour moi).

    Si la classe Pneu se voit dotée d'une nouvelle méthode, alors il faudra que je modifie la classe voiture si je veux accéder à cette méthode...
    En ajoutant par exemple Pneu::modifieDessin(), je dois ajouter Voiture::modifieDessinPneu().... cela me semble inutile et ralentira le process de dev.

    Alors qu'un getter m'autorisera à profiter de la nouvelle méthode sans rien changer alentour !

    Dès lors, je ne peux pas me ranger au principe de Demeter, car ma classe voiture n'a pas de raison de changer tant que la voiture elle-même n'a pas été modifiée structurellement.

    Mais quelque chose me dit qu'il s'agit là d'un cas ou on a le droit de s'en affranchir....

    Ma voiture n'est pas qu'un simple struct regroupant des instances de classes, car les setters ne font pas que remplacer un composant de la voiture, ils en profitent également pour garantir la cohérence de l'objet,
    par exemple, ne pas installer de pneus trop large. (en référence à un autre post qui explique entités et comportements ou un truc du genre...)

    Comment alors justifier de ne pas avoir accès aux éléments de ma voiture (getters) ?

    Je suis rarement géné dans mes développement qui sont la plupart du temps très évolutifs. Je suis très intuitif et j'essaie ces temps ci de me remettre en cause sur mes méthodes de developpement, cela dit; je m'aperçoit que je respecte finalement beaucoup de concepts nouveau pour moi dans la dénomination, mais très anciens dans ce que je fais (par concept, j'entends SRP, OCP, LSP, ISP, DIP).
    Et quand je suis géné, je le sais généralement au moment même ou j'écris le code futur-bloquant. Mais sous la pression, et les délai, cela peut encore m'arriver.

    Et je viens d'acheter le livre de Philippe et Luc :-D pour confronter mes idées et mon expérience personnelle (30 ans).

  13. #93
    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
    Il est toujours envisageable de filtrer en amont la liste de pneus compatibles. L'expérience n'en sera que meilleure pour l'utilisateur -> au lieu de sélectionner dans une liste de 150 éléments et de s'en voir refuser 100 pour motif de "ah ben non, c'est pas compatible avec la forme choisie!", il ne pourra choisir que parmi 50 éléments possibles dans la combobox. La possibilité de saisir un modèle non compatible devient alors une erreur de programmation.

    Pour se qui est du redraw si un élément à afficher est modifié... Là, c'est la preuve pour moi que c'est bien plus qu'un simple setter -- qui serait alors un très mauvais nom. De plus, la question du mustRedraw me parait dépasser la notion de : "on change une roue alors on retrace". Quid si la vue courante est celle du tableau de bord et que l'on centre sur l'intérieur de la boite à gants qui est côté passager ? On s'en fout un peu des roues qui changent, non ?
    Mais bon, je parle un peu dans le vide je pense. Il est facile de proposer des architectures sur des sujets que l'on connait mal. Je pense qu'il faut un peu plus de bouteille côté métier pour discuter de chaque exemple.

    EDIT: Je dirai que dans l'idéal on essaie d'éviter les setters -- je m'autorise des dérogations pour les getters souvent. Et parfois ... ben les contraintes ou l'inspiration du moment font que l'on en met. De plus, certains domaines sont par tradition plus propices à l'emploi de setters. Tu en a justement pris un je pense.
    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...

  14. #94
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    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 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Ta classe voiture ressemble surtout à un gros POD et pas une classe de service.

    Une voiture en "mode service" aurait "RemplirReservoire", "ReparerPareBrise", "AllumerAutoRadio" etc... bref, c'est la voiture au quotidien pour un utilisateur.
    La tienne a une liste de setter/getter pour chaque élément, c'est la voiture pour le garagiste, dans son garage.
    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.

  15. #95
    Futur Membre du Club
    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    5
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 5
    Points : 5
    Points
    5
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    Il est toujours envisageable de filtrer en amont la liste de pneus compatibles. L'expérience n'en sera que meilleure pour l'utilisateur -> au lieu de sélectionner dans une liste de 150 éléments et de s'en voir refuser 100 pour motif de "ah ben non, c'est pas compatible avec la forme choisie!", il ne pourra choisir que parmi 50 éléments possibles dans la combobox. La possibilité de saisir un modèle non compatible devient alors une erreur de programmation.
    Hmmm, j'étais prêt à contre-argumenter (filtrage en amont), mais en extrapolant cette remarque, on pourrait effectivement faire

    maVoiture->getOneComponent()->modifySomething()

    Ce qui fait que la voiture serait complètement ignorante du fait que l'on ait modifié something à l'un de ses composants. Et de ce fait, corrompre l'état de l'objet (en terme de cohérence).


    Pour se qui est du redraw si un élément à afficher est modifié... Là, c'est la preuve pour moi que c'est bien plus qu'un simple setter -- qui serait alors un très mauvais nom. De plus, la question du mustRedraw me parait dépasser la notion de : "on change une roue alors on retrace". Quid si la vue courante est celle du tableau de bord et que l'on centre sur l'intérieur de la boite à gants qui est côté passager ? On s'en fout un peu des roues qui changent, non ?
    Mais bon, je parle un peu dans le vide je pense. Il est facile de proposer des architectures sur des sujets que l'on connait mal. Je pense qu'il faut un peu plus de bouteille côté métier pour discuter de chaque exemple.
    Ce deuxième point pourrait être résolu par le pattern observer non ?

    EDIT: Je dirai que dans l'idéal on essaie d'éviter les setters -- je m'autorise des dérogations pour les getters souvent. Et parfois ... ben les contraintes ou l'inspiration du moment font que l'on en met. De plus, certains domaines sont par tradition plus propices à l'emploi de setters. Tu en a justement pris un je pense.
    Cela me rassure finalement. Car j'ai regardé mon code actuel (assez conséquent en nombre de classes) et j'ai finalement très peu recours aux getters, et encore moins aux setters. Et Demeter me semble globalement respecté. Ce qui me dérange dans mes devs, c'est que je fais bien, mais qu'il me manque ces aspects théoriques pour pouvoir les expliquer aux autres (intuition est synonyme de manque d'argumentation pour promouvoir mes idées).

    Et puis généralement, mes getters sont const (il le sont systématiquement au premier jet, et peuvent éventuellement devenir non const ensuite).

    Merci pour vos réponses

  16. #96
    Futur Membre du Club
    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    5
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 5
    Points : 5
    Points
    5
    Par défaut Classe aggrégée modifiée
    J'ai avancé sur le sujet, en revanche, je ne peux toujours pas tolérer d'appliquer Demeter sur le point numéro 2 que je répète ici :

    2 - Pire encore, la classe voiture dépend complètement de chaque classe aggregée !!! (Et la, ça ne passe pas pour moi).

    Si la classe Pneu se voit dotée d'une nouvelle méthode, alors il faudra que je modifie la classe voiture si je veux accéder à cette méthode...
    En ajoutant par exemple Pneu::modifieDessin(), je dois ajouter Voiture::modifieDessinPneu().... cela me semble inutile et ralentira le process de dev.
    Comment concilier Demeter dans ce cas avec la(ma?) volonté qu'une modification de code soit toujours la plus isolée possible ?

    De même, si Pneu a bien pour méthode modifieDessin, puisque c'est son rôle, en quoi s'agirait-il d'une responsabilité de Voiture ?

    Pour enfoncer le clou : une Voiture possède (généralement ) quatre pneus....

    Faudrait-il alors ajouter à la classe voiture

    void setDessinPneuArGauche(...);
    void setDessinPneuAvGauche(...);
    void setDessinPneuArDroit(...);
    void setDessinPneuAvDroit(...);

    Ansi de suite pour TOUTES les méthodes de pneu ???
    Cela veut dire que si j'ajoute une méthode à Pneu, je dois ajouter QUATRE méthodes à Voiture... Vraiment, je ne peux pas m'y résoudre.

    Pour ma part, quatre getters non const pour chaque pneu et voilou. Je n'ai plus à modifier la voiture lorsque l'interface de l'un de ses composant est étendue.

    Pour un camion, il pourrait alors s'agir de 6 ou 8 pneus....

    Par ailleurs

    Imaginons que la classe Roue soit utilisée dans 150 classes, il faudrait alors modifier 150 classes pour avoir accès à setDessin() ???
    Cela me paraît complètement anti-programmation objet, anti productif. Bref, un non sens (je vais avoir du mal avec ce Demeter )

    Et pour ce véhicule, on fait comment ??? http://i0.wp.com/www.redferret.net/w...urch.png?w=980 (OK je sors )

  17. #97
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Pour commencer, même si je faisait un accesseur, j'utiliserai une enum (ou autre) pour dire quel pneu je veux.
    Dans un vector, tu n'a pas de fonctions get1(), get2() etc
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  18. #98
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    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 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    J'adore comment tu détournes une règle pour un cas particulier foireux.
    Au lieu d'avoir un getter par roue, tu pourrais avoir une méthode "ReplacePneu", et lui passer en paramètre le pneu que tu mets.
    Et "ajouter un dessin à un pneu", il suffit de le voir comme "remplacer par un pneu avec dessin".
    mavoiture->SetPneu(id_pneu, mon_joli_pneu_tune_auparavant)
    Ta classe voiture ne dépend de rien du tout. Elle aggrège d'autres classes, et alors ? Bienvenue dans le vrai monde de la programmation...
    Une structure de base Pneu dont héritera chaque pneu p-e ? Et la voiture n'a rien à faire de leur spécialisation.
    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.

  19. #99
    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
    (Avant toute chose : bravo pour le déterrage de post... Il n'avait "que" cinq ans, mais quand meme )

    Je suis tout à fait d'accord avec bousk dans le sens où tu n'as absolument pas besoin d'accéder au pneu, à la jante ou à n'importe quoi de manière "physique" pour pouvoir le modifier.

    Au pire, ta voiture expose un certain nombres de fonctions qui te permettent, de connaitre le nombre de (sieges, roues, portes, ...) et tu y associe des fonctions -- prenant "quel" (siege, roue, porte) changer le modèle de (siege, roue, porte) de remplacement à chacune de ces fonctions, en prévoyant la possibilité (selon tes besoins) soit de changer "tous les éléments" d'un seul coup soit de n'en changer qu'un seul bien particulier.

    Mais, de manière générale, la voiture n'en a absolument rien à foutre du dessin des pneus dont elle est équipée, ni de la couleur de la porte avant droite... Tout ce qui lui importe, c'est d'avoir autant de jantes, et de pneus (les essieux + la roue de secours ) , de sièges, de portes et de fenêtres qu'elle n'a d'espaces pour pouvoir les mettre.

    Soit, cela prend la forme d'une hiérarchie de pneus (de jantes, de sièges, ...) et tu chaque élément est représenté dans ta classe par un pointeur sur l'élément en question, soit tu utilises une approche plus proche d'un ECS et tu limite la connaissance de chaque élément que la voiture peut en avoir à ... une valeur numérique identifiant chaque "type" d'élément de manière unique (c'est sans doute plus facile à maintenir sous cette forme ) mais, quoi qu'il en soit, tu as toujours la possibilité d'envisager d'exposer des comportements permettant de modifier les éléments utilisés par ta voiture par "groupe d'éléments" au lieu de multiplier les mutateurs
    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

  20. #100
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 630
    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 630
    Points : 10 556
    Points
    10 556
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Soit, cela prend la forme d'une hiérarchie de pneus (de jantes, de sièges, ...) et tu chaque élément est représenté dans ta classe par un pointeur sur l'élément en question, soit tu utilises une approche plus proche d'un ECS et tu limite la connaissance de chaque élément que la voiture peut en avoir à ... une valeur numérique identifiant chaque "type" d'élément de manière unique (c'est sans doute plus facile à maintenir sous cette forme ) mais, quoi qu'il en soit, tu as toujours la possibilité d'envisager d'exposer des comportements permettant de modifier les éléments utilisés par ta voiture par "groupe d'éléments" au lieu de multiplier les mutateurs
    Fabrique abstraite/ Abstract Factory

Discussions similaires

  1. Que pensez-vous des générateurs de doc PHP ?
    Par Nonothehobbit dans le forum EDI, CMS, Outils, Scripts et API
    Réponses: 64
    Dernier message: 10/07/2007, 10h17
  2. Que pensez vous de filemaker
    Par thpopeye dans le forum Autres SGBD
    Réponses: 4
    Dernier message: 14/06/2007, 15h20
  3. Que pensez vous du nouveau kernel 2.6 ?
    Par GLDavid dans le forum Administration système
    Réponses: 58
    Dernier message: 02/08/2004, 15h45
  4. [Débat] Que pensez-vous des langages à typage dynamique?
    Par Eusebius dans le forum Langages de programmation
    Réponses: 14
    Dernier message: 16/06/2004, 12h12
  5. Que pensez vous du mariage ASP Flash?
    Par tyma dans le forum Flash
    Réponses: 4
    Dernier message: 09/07/2003, 15h00

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