Entièrement d'accord !:ccool:
Version imprimable
Le principe de demeter n'indique pas qu'il faille mettre tout à plat pour éviter de le violer. Mettre tout à plat en ajoutant 1000&1 méthodes pour ne pas le violer, c'est tout simplement mal corriger un problème de conception. En général, si tu commences à trop enfreindre cette loi ou que ta classe a trop de méthodes pour éviter de l'enfreindre, c'est probablement qu'il y a un problème de conception et que le niveau d'abstraction n'est pas le bon.
Là, tu joues sur les mots. Elle dit effectivement que tout DOIT être à plat ! (bon, elle dit pas que ça, mais c'est sur ce point que je critique).
Attention à ne pas partir dans trop d'extrémisme. Je sais, je suis le premier à avoir parler de 40 inlines (oO), mais tâchons de rester sobre :
-> Une interface (ou un objet) qui, d'un point de vue externe, sont insécable et avec uniquement que des méthodes utiles.
Reste le problème soulevé toujours présent... C'est une question de "Bon sens" (TheNOHDirector).
:koi:
A aucun moment on parle de mettre quoi que ce soit à plat. Je maintient, comprendre : la loi implique qu'il faut mettre à plat pour pouvoir accéder aux objets c'est faire fi de l'ensapsulation et l'abstraction. Quitte à me répéter, c'est se tromper sur la façon dont résoudre le problème. Si une méthode a besoin d'appeler une méthode d'un objet obtenu via un autre, il y a des risques que l'objet expose des détails d'implémentation ou que son niveau d'abstraction ne soit pas le bon.Citation:
Envoyé par Loi Demeter
--> Oui tant que c++03. Cf un de mes posts précedent.
C'est un autre problème. Je ne considère pas se cas là (dans se cas, il y quelque chose à faire pour se rapprocher de Demeter).Citation:
ou que son niveau d'abstraction ne soit pas le bon.
La loi dit clairement qu'on ne doit pas accéder au méthode d'un retour de méthode (généralement getters). Donc on a le droit qu'a un seul niveau d'accès en profondeur (<= mise à plat). Peut importe le comment de la mise à plat d'ailleurs.
+1 pour le "petit voyant", sans parler des setters.
Oui d'une manière générale les getter/setter ne sont plus trop recommandés, car ils brisent l'encapsulation. Mais là encore il faut savoir faire la part des choses, est-ce qu'on parle d'objet du domaine métier (avec leur intelligence) ou est-ce qu'on parle d'objet POJO (ou PODS en C++).
Dans les deux cas la conception et le design des POJO autant que des objets métier. Après vient encore la maintenance du code, qui complique encore la tache, ou on peut se retrouver avec du code qui est loin de la loi de Demeter.
Je suis également d'accord avec 3DArchi, à un certain moment les problèmes rencontrés révèle très certainement un problème de design. Dans l'absolu j'essaye de suivre les bons principes.
Petit article à lire : http://butunclebob.com/ArticleS.Uncl...rinciplesOfOod
Kent Beck a aussi écrit des bouquins intéressants.
Faire du TDD peut aider à faire du code maitrisé, focalisé, maintenanble et bien sur testé, mais ça ne fait pas tout.
Encore une fois il faut du bon sens, comme partout!
Marrant... Je travaille sur quelque chose d'assez similaire, ces temps ci, et je suis une approche assez similaire, même si mes structures ne sont pas un décalque parfait du xml (au niveau le plus bas, je ne fais pas de structures mais des champs, directement).
Pour les fonctions communes (lecture, écriture des xml, et traitements de base), j'utilise des surcharges d'opérateurs, ou des fonctions homonymes, ce qui permet d'éviter de cribler le code de if() et de switch(). C'est l'amélioration que je te conseillerais. Il faut juste les documenter précisément.
Certes, ce ne sont pas des "bonnes pratique", mais il en résulte un code extrèmement facile à maintenir, parce qu'il suit de très près la structure du fichier. D'une certaine manière, la spec de ton xml est la documentation de ton programme. Par ailleurs, si un bout de la spec xml change, je sais immédiatement où regarder dans mon code, et je sais que tout mainteneur futur y arrivera également.
Le programme résultat est tout à fait inélégant, et contraire au principe selon lequel il faut éviter les redondances de code (chez moi, il y en a partout, les plus évidentes sont dans des fonctions statiques externes, mais il en reste beaucoup). Mais on échange un code compact contre un code facile à lire, et plus je vieillis, plus je me dis que c'est souvent une bonne idée...
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it" (déboguer est deux fois plus difficile qu'écrire le code d'origine, aussi, si vous écrivez le code le plus intelligent qui soit, vous n'êtes, par définition, pas assez malin pour le déboguer), comme disait l'autre...
Francois
Ton 7 +/-2 est correcte, certes, mais tu parles toi-même d'éléments. Pas de caractères. 5 à 7 paramètres, on s'en souvient vaguement. 5 à 7 mots dans un identifiants, on s'en souviendra aussi bien - et ça commence à faire un pétard de long identifiant :)
Oui...
Non :)
Le SRP ne dit pas de partitionner les classes (même si dans la pratique, ça reviens vaguement à ça) ; il dit de s'assurer qu'une classe ne possède qu'une seule responsabilité. Demeter ne dit pas "faite comme si rien n'était partitionné", elle dit "encapsulez les traitements afin de ne pas avoir besoin de désencapsuler l'accès aux données". Les données étant intrinsèques à une classe, il n'est pas normal de pouvoir les manipuler de l'extérieur.
Dans la pratique, le SRP et la loi de Demeter ne sont pas contradictoire - bien au contraire. Les deux proposent une approche complémentaire de l'encapsulation visant à s'assurer d'une part que le code sera lisible et d'autre part que le code sera bien construit.
Si j'ai bien compris les rrefs, il ne s'agit pas là d'une des utilisations envisagées - en tout cas, pas comme moyen de contourner l'encapsulation.
Dans la pratique, cet être horrible cesse d'émettre la moindre idée dès lors qu'il entend des mots qu'il ne comprends pas. Et s'il comprends tous les mots compliqués que tu va lui sortir, alors il se pourrait qu'il ne soit pas si horrible que ça et que ses conseils aient un sens :)
... et puisque tu n'avais pas à l'époque les connaissances suffisantes pour lui assurer que ce n'était pas la bonne solution, tu as été obligé d'obéir. Ne t'inquiète pas : c'est le métier qui rentre... :)
Etonament, tu n'es pas la seule personne à t'être posé la question :) Plus sérieusement, de nombreux programmes manipulent des données XML, et globalement :
* soit le XML est pensé correctement, et dans ce cas la solution s'impose presque d'elle même : chaque tag est un objet, avec ses méthode et ses données. Il n'y a pas de raison pour que les données soient publiques ou même qu'elles soient exposées avec des mutateurs quelconques.
* soir le XML est bancal, et il devient difficile de construire une abstraction à partir de ce dernier. Le traitement doit se faire en deux phases :
1) lecture du XML et stockage des données
2) construction d'un arbre d'objets correctement architecturé à partir de ces données.
Notons que le problème est le même pour toute autre représentation (base de donnée, fichier binaire, ...). Le fait de récupérer des données dans une base de donnée (au sens large : fichier, SGBD, ...) n'impose pas nécessairement une architecture logicielle particulière. Je dirais même plus : dans de nombreux cas, ça n'est pas souhaitable.
Ce n'est pas vraiment un problème de sémantique mais bien (comme je l'ai dis avant) de taxonomie.
* un principe doit être respecté - c'est une base de travail. Ne pas respecter un principe conduit à construire une architecture qui sera encombrée de nombreux problèmes et qui rendra la maintenance du produit difficile et couteuse. Les grands principes de deisgn orienté objet sont un peu les axiomes de l'architecture.
* de ces principes, on tire des corolaires. Les corolaires sont un peu les théorèmes de notre métier. Ils nous disent "si tu fait ça, il va se passer ça".
* en dehors des principes et des corolaires, il y a les lois, qui se rangent en plusieurs catégories :
- les lois d'architecture, qui proposent des solutions permettant d'améliorer l'architecture logicielle d'un produit
- les lois de style (comme la loi de Demeter) qui se proposent d'améliorer la qualité du code produit
- les lois de processus (par exemple les "principes" d'XP) qui se proposent de définir un cadre pour effectuer notre travail (par exemple la programmation par paire).
Appliquer les lois supposent qu'on a établi un contexte dans lequel on peut les appliquer. En ce qui concerne les lois d'architecture, ce contexte est créé en appliquant les principes fondamentaux (OCP, LSP, SRP, DIP, ...). Le contexte nécessaire à l'application des lois de style est l'écriture du code, qui ne peut se faire que si une architecture a été devisée. Enfin, les lois de processus sont applicables dès lors que la notion de projet a été posée.
Donc on va rester avec le terme "loi de Demeter", parce que c'est bel et bien une loi et non pas un principe.
Je réserve pour l'instant mon jugement sur Demeter, je n'ai pas assez de retour d'expérience dessus pour juger, d'où mon silence sur ce thread que je lis attentivement.
Mais ici je suis un peu surpris par la version que tu en donnes. Tu parles de désencapsuler l'accès aux données. Tel que j'avais cette loi en tête jusqu'à présent, elle me semblait plus stricte. C'est à dire que monObjet.traitement1().traitement2() était interdit par cette loi, et pas uniquement monObjet.getData().traitement().
La différence peut sembler minime, mais elle me semble significative, car dans cette version "assouplie", Demeter peut n'être qu'une autre version de "éviter les getters/setters, ils brisent généralement l'encapsulation".
Qu'en est-il ?
En fait, tout dépend du type renvoyé par traitement1()...
Il n'a jamais été dit qu'aucune fonction membre ne devait renvoyer de valeur!!!
Il a juste été dit que les valeurs de retour sont restreintes et peuvent peu ou prou entrer dans:Si donc traitement1() renvoie une référence sur (une copie de) l'objet en cours, cela ne me choque absolument pas...
- Les types primitifs ou assimilables (je pense par exemple à std::string comme type assimilable), en veillant cependant, dans le cas de std::string à en renvoyer soit une copie soit une référence constante.
- L'objet lui-même (ou une copie de l'objet lui-même)
Par contre, si traitement1() agit, pour faire simple, comme un getter "amélioré" et renvoie un membre de l'objet, cela me semble déjà beaucoup plus choquant.
Le tout modulo, bien évidemment, le fait que l'objet agisse en réalité comme... une collection ;)
Il faudrait que j'aille voir les sources originales. L'idée de fond est peut-être intéressante, mais les présentations que j'ai vues me semble juste pousser à écrire des wrappers inutiles.
Comme on ne peut pas faire
on va écrireCode:
1
2
3
4 void C::f(A a) { a.g().h(); }
avec Bh dupliqué à un tas d'endroit. A moins qu'on le pousse dans A où il sera implémentéCode:
1
2
3
4
5
6
7
8
9 void C::Bh(B b) { b.h(); } void C::f(A a) { Bh(a.g()); }
ce qui ne me semble pas nécessairement mieux. Mais ça dépend pas mal des rapports entre les classes.Code:
1
2
3
4
5
6
7
8
9 void A::Bh(B b) { b.h(); } void A::Bh() { Bh(g()); }
Bonsoir,
Personnellement, j'ai l'impression que quand on fait quelque chose comme l'exemple que tu montres, on tombe un peu dans le travers de vouloir coller à un principe dans l'implémentation plutôt que de l'intégrer dans la conception. En d'autres termes, on demande à f(a) d'en savoir trop sur a pour réaliser son service.
En fait je dirais plus qu'au lieu de faire :
Puis :Code:C::C( A a ) : a_( a ) {}
On ferait :Code:C::f() { a_.g().h(); }
Et directement :Code:C::C( B b ) : b_( b ) {}
Ok, ça c'est dans le cas où C n'a par ailleurs pas besoin de l'instance de A, mais même si c'était le cas :Code:C::f() { b_.h(); }
Le fait que A ait aussi besoin d'une instance de B concerne A uniquement, et pas C.Code:C::C( A a, B b ) : a_( a ), b_( b ) {}
Si une classe a besoin d'une instance d'une autre pour fonctionner, on lui fournit directement plutôt que d'aller la récupérer par des moyens détournés ailleurs.
MAT.
Comme je l'ai écrit, il faudrait que j'aille voir à la source quel est l'objectif. Ce qui est vraisemblable, c'est qu'il est trop facile de respecter la formulation syntaxique sans respecter l'objectif.
J'écris beaucoup de code qui ne respecte pas ce principe. Cela tient peut-être à la nature des rapports entre les classes, mais tous les moyens que je vois pour le respecter me semblent artificiels (la seule classe de solution que je n'ai pas réellement envisagées, c'est de modifier les interfaces des classes dont je suis le client).
Salut,
C'est un peu ce que je voulais dire. Si on se contente d'artifices pour respecter par la syntaxe ce principe, alors on va probablement pas bénéficier des objectifs à la base (qui sont essentiellement moins de couplage et une indépendance sur l'implémentation d'une classe).
En réfléchissant, je me suis rendu compte qu'il y avait aussi des cas où je ne respectait pas ce principe, souvent sur de l'I.H.M. Je peux avoir par exemple dans une boîte dialogue à récupérer un contrôle et à invoquer une méthode sur ce contrôle. Mais j'ai tendance à penser que si syntaxiquement un contrôle n'est pas membre de la boîte de dialogue, d'une certaine façon sémantiquement, si. J'imagine qu'il peut y avoir d'autres cas similaires.
Ce qui est étonnant, c'est qu'on a eu une discussion de 3 pages presque sans aborder ce point qui est pourtant fondamental. Faire le point, ça peut aussi être une bonne manière de faire avancer le débat.
Dans sa définition, Demeter étant une loi de style, il semblerait qu'on puisse l'appliquer partout. Ce n'est évidemment pas le cas, à moins de recourir à des artifices plutôt... artificiels. Demeter nécessite donc de faire des choix d'architecture logicielle qui vont amener à son application naturelle. En gros : ce n'est pas l'application de la loi de Demeter qui rendra le code meilleur, c'est le fait qu'on puisse l'utiliser et qu'on le fasse qui nous intéresse vraiment.
Ceux qui veulent vraiment voir l'effet que ça peut avoir sur le code devraient se forcer à créer un petit projet orienté objet avec les caractéristiques suivantes (sans même forcément appliquer la loi de Demeter):
* pas de référence circulaire dans le graphe des classes.
* toutes les données sont privées. Pas protected. Pas public. Privées.
* pas de mutateurs du tout ; on encapsule les traitements, pas les données.
Bien sûr, les principes de base (OCP, LSP, SRP, DIP, ISP) doivent aussi être respectés.
Une fois que vous avez le code, faite une passe de refactoring pour appliquer la loi de Demeter - normalement, son application devrait être naturelle dans ce cas. En fait, vous devriez voir que le nombre de places ou vous aurez à changer le code est très restreint - tout simplement parce que votre design est déjà très Demeter-friendly.
C'est peut-etre lie a mon domaine d'application, mais ca me semblerait extremement artificiel. Il y a des classes qui sont intrinsequement liees et vouloir les decoupler n'a absolument aucun sens.
Je me demande si la cause de ma gene ne serait pas que, dans les domaines ou j'ai travaille, la classe est un niveau de decoupage trop fin pour ce genre de regles. (Autrement dit, je crois comprendre l'objectif, il me semble louable, mais le niveau me semble inadapte).
Aucun probleme avec ca.Citation:
* toutes les données sont privées. Pas protected. Pas public. Privées.
Globablement d'accord mais la limite est parfois ambigue. Si tu refuses setPosition() mais que tu admets moveTo() (le deux faisant exactement la meme chose), ca ne me gene vraisemblablement pas.Citation:
* pas de mutateurs du tout ; on encapsule les traitements, pas les données.