Il y a le problème classique de POO de chaînage de méthodes vs méthodes de "point d'accès unique":
main.getA().getB().getC().transmogrify(x, y)
contre
main.getA().transmogrifyMyC(x, y)
La première semble avoir l'avantage que chaque classe n'est responsable que d'un ensemble plus petit d'opérations, et rend tout beaucoup plus modulaire - l'ajout d'une méthode à C ne nécessite aucun effort en A, B ou C pour l'exposer.
L'inconvénient, bien sûr, est une encapsulation plus faible , que le deuxième code résout. A contrôle maintenant toutes les méthodes qui la traversent et peut la déléguer à ses champs s'il le souhaite.
Je me rends compte qu'il n'y a pas de solution unique et cela dépend bien sûr du contexte, mais j'aimerais vraiment entendre des commentaires sur d'autres différences importantes entre les deux styles, et dans quelles circonstances devrais-je préférer l'un ou l'autre - parce qu'en ce moment, quand j'essaie pour concevoir du code, j'ai l'impression de ne pas utiliser les arguments pour décider d'une manière ou d'une autre.
J'essaie en général de garder la méthode d'enchaînement aussi limitée que possible (basée sur la loi de Déméter )
La seule exception que je fais est pour les interfaces fluides / programmation de style DSL interne.
Martin Fowler fait en quelque sorte la même distinction dans les langages spécifiques au domaine, mais pour des raisons de séparation des requêtes de commande de violation, qui stipule:
Fowler dans son livre à la page 70 dit:
la source
Je pense que la question est de savoir si vous utilisez une abstraction appropriée.
Dans le premier cas, nous avons
Où principal est de type
IHasGetA
. La question est: cette abstraction est-elle appropriée? La réponse n'est pas anodine. Et dans ce cas, cela semble un peu décalé, mais c'est quand même un exemple théorique. Mais pour construire un exemple différent:Est souvent meilleur que
Parce que dans ce dernier exemple, les deux
this
etmain
dépendent des types dev
,w
,x
,y
etz
. De plus, le code ne semble pas vraiment meilleur, si chaque déclaration de méthode a une demi-douzaine d'arguments.Un localisateur de services nécessite en fait la première approche. Vous ne voulez pas accéder à l'instance qu'il crée via le localisateur de services.
Ainsi, le fait de "traverser" un objet peut créer beaucoup de dépendances, d'autant plus s'il est basé sur les propriétés de classes réelles.
Cependant, créer une abstraction, c'est tout fournir un objet, c'est tout autre chose.
Par exemple, vous pourriez avoir:
Où
main
est une instance deMain
. Si la classe sachantmain
réduit la dépendance à laIHasGetA
place deMain
, vous constaterez que le couplage est en fait assez faible. Le code appelant ne sait même pas qu'il appelle en fait la dernière méthode sur l'objet d'origine, ce qui illustre en fait le degré de découplage.Vous atteignez le long d'un chemin d'abstractions concises et orthogonales, plutôt que profondément dans les internes d'une implémentation.
la source
le loi de Déméter , comme le souligne @ Péter Török, suggère la forme "compacte".
En outre, plus vous mentionnez explicitement de méthodes dans votre code, plus votre classe dépend de classes, ce qui augmente les problèmes de maintenance. Dans votre exemple, la forme compacte dépend de deux classes, tandis que la forme plus longue dépend de quatre classes. La forme plus longue ne contrevient pas seulement à la loi de Déméter; il vous fera également changer votre code chaque fois que vous modifiez l'une des quatre méthodes référencées (par opposition à deux dans le format compact).
la source
A
va exploser avec beaucoup de méthodes queA
vous voudrez peut-être déléguer de toute façon. Pourtant, je suis d'accord avec les dépendances - cela réduit considérablement la quantité de dépendances requises du code client.J'ai moi-même lutté avec ce problème. L'inconvénient de "pénétrer" profondément dans différents objets est que lorsque vous refactorisez, vous finirez par devoir modifier énormément de code car il y a tellement de dépendances. De plus, votre code devient un peu gonflé et plus difficile à lire.
D'un autre côté, avoir des classes qui "transmettent" simplement les méthodes signifie également un surcoût de devoir déclarer un multiple de méthodes à plusieurs endroits.
Une solution qui atténue cela et qui est appropriée dans certains cas est d'avoir une classe d'usine qui construit des objets de façade en copiant les données / objets des classes appropriées. De cette façon, vous pouvez coder contre votre objet de façade et lorsque vous refactorisez, vous changez simplement la logique de l'usine.
la source
Je trouve souvent que la logique d'un programme est plus facile à comprendre avec des méthodes chaînées. Tome,
customer.getLastInvoice().itemCount()
s'inscrit mieux dans mon cerveaucustomer.countLastInvoiceItems()
.Que cela vaille la peine de maintenance d'avoir le couplage supplémentaire dépend de vous. (J'aime aussi les petites fonctions dans les petites classes, donc j'ai tendance à enchaîner. Je ne dis pas que c'est juste - c'est juste ce que je fais.)
la source