Le principe Tell Don't Ask dit:
vous devez essayer de dire aux objets ce que vous voulez qu'ils fassent; ne leur posez pas de questions sur leur état, ne prenez pas de décision, puis dites-leur quoi faire.
Le problème est que, en tant qu'appelant, vous ne devez pas prendre de décisions basées sur l'état de l'objet appelé, ce qui vous amène à changer ensuite l'état de l'objet. La logique que vous implémentez est probablement la responsabilité de l'objet appelé, pas la vôtre. Pour vous, prendre des décisions en dehors de l'objet viole son encapsulation.
Un exemple simple de «Dites, ne demandez pas» est
Widget w = ...;
if (w.getParent() != null) {
Panel parent = w.getParent();
parent.remove(w);
}
et la version tell est ...
Widget w = ...;
w.removeFromParent();
Mais que faire si j'ai besoin de connaître le résultat de la méthode removeFromParent? Ma première réaction a été simplement de changer le removeFromParent pour retourner un booléen indiquant si le parent a été supprimé ou non.
Mais ensuite, je suis tombé sur le modèle de séparation des requêtes de commande qui dit de ne pas faire cela.
Il indique que chaque méthode doit être soit une commande qui exécute une action, soit une requête qui renvoie des données à l'appelant, mais pas les deux. En d'autres termes, poser une question ne devrait pas changer la réponse. Plus formellement, les méthodes ne doivent renvoyer une valeur que si elles sont référentiellement transparentes et ne possèdent donc aucun effet secondaire.
Ces deux-là sont-ils vraiment en contradiction l'un avec l'autre et comment choisir entre les deux? Dois-je aller avec le programmeur pragmatique ou Bertrand Meyer à ce sujet?
Réponses:
En fait, votre exemple de problème illustre déjà le manque de décomposition.
Modifions-le un peu:
Ce n'est pas vraiment différent, mais rend la faille plus apparente: pourquoi le livre connaîtrait-il son étagère? Autrement dit, cela ne devrait pas. Il introduit une dépendance des livres sur les étagères (ce qui n'a pas de sens) et crée des références circulaires. C'est tout mauvais.
De même, il n'est pas nécessaire qu'un widget connaisse son parent. Vous direz: "D'accord, mais le widget a besoin de son parent pour se mettre en page correctement, etc." et sous le capot, le widget connaît son parent et lui demande ses métriques pour calculer ses propres métriques en fonction d'eux, etc. Selon tell, ne demandez pas que c'est faux. Le parent doit dire à tous ses enfants de rendre, en passant toutes les informations nécessaires comme arguments. De cette façon, on peut facilement avoir le même widget chez deux parents (que cela ait du sens ou non).
Pour revenir à l'exemple du livre - entrez le bibliothécaire:
S'il vous plaît, comprenez que nous ne demandons plus dans le sens de dire, ne demandez pas .
Dans la première version, l'étagère était une propriété du livre et vous l'avez demandé. Dans la deuxième version, nous ne faisons aucune hypothèse de ce type sur le livre. Tout ce que nous savons, c'est que le bibliothécaire peut nous indiquer une étagère contenant le livre. Vraisemblablement, il s'appuie sur une sorte de table de consultation pour le faire, mais nous ne savons pas avec certitude (il pourrait également parcourir tous les livres de chaque étagère) et en fait, nous ne voulons pas savoir .
Nous ne nous appuyons pas sur la question de savoir si ou comment la réponse des bibliothécaires est couplée à son état ou à celui d'autres objets dont elle pourrait dépendre. Nous demandons au bibliothécaire de trouver l'étagère.
Dans le premier scénario, nous avons codé directement la relation entre un livre et une étagère dans le cadre de l'état du livre et récupéré cet état directement. Ceci est très fragile, car nous supposons également que l'étagère rendue par le livre contient le livre (c'est une contrainte que nous devons garantir, sinon nous pourrions ne pas être en mesure de retirer
remove
le livre de l'étagère dans laquelle il se trouve réellement).Avec l'introduction du bibliothécaire, nous modélisons cette relation séparément, réalisant ainsi une séparation des préoccupations.
la source
Si vous avez besoin de connaître le résultat, vous le faites; c'est votre exigence.
L'expression «les méthodes ne doivent renvoyer une valeur que si elles sont référentiellement transparentes et ne présentent donc aucun effet secondaire» est une bonne ligne directrice à suivre (surtout si vous écrivez votre programme dans un style fonctionnel , pour des accès concurrents ou pour d'autres raisons), mais c'est pas un absolu.
Par exemple, vous devrez peut-être connaître le résultat d'une opération d'écriture de fichier (vrai ou faux). C'est un exemple de méthode qui renvoie une valeur, mais produit toujours des effets secondaires; il n'y a aucun moyen de contourner cela.
Pour effectuer la séparation commande / requête, vous devez effectuer l'opération de fichier avec une méthode, puis vérifier son résultat avec une autre méthode, une technique indésirable car elle dissocie le résultat de la méthode qui a provoqué le résultat. Que se passe-t-il si quelque chose arrive à l'état de votre objet entre l'appel de fichier et la vérification d'état?
L'essentiel est le suivant: si vous utilisez le résultat d'un appel de méthode pour prendre des décisions ailleurs dans le programme, vous ne violez pas Tell Don't Ask. Si, en revanche, vous prenez des décisions pour un objet en fonction d'un appel de méthode à cet objet, vous devez déplacer ces décisions dans l'objet lui-même pour préserver l'encapsulation.
Ce n'est pas du tout contradictoire avec la séparation des requêtes de commande; en fait, il l'applique davantage, car il n'est plus nécessaire d'exposer une méthode externe à des fins de statut.
la source
Je pense que vous devriez suivre votre instinct initial. Parfois, la conception doit être intentionnellement dangereuse, pour introduire de la complexité immédiatement lorsque vous communiquez avec l'objet afin que votre forcé le gère correctement immédiatement, au lieu d'essayer de le manipuler sur l'objet lui-même et de masquer tous les détails sanglants et de le rendre difficile à nettoyer proprement modifier les hypothèses sous-jacentes.
Vous devriez avoir un sentiment de naufrage si un objet vous propose des équivalents
open
et desclose
comportements implémentés et vous commencez immédiatement à comprendre ce que vous traitez lorsque vous voyez une valeur de retour booléenne pour quelque chose que vous pensiez être une simple tâche atomique.Comment vous vous en sortirez plus tard, c'est votre truc. Vous pouvez créer une abstraction au-dessus, un type de widget avec
removeFromParent()
, mais vous devriez toujours avoir un repli de bas niveau, sinon vous feriez peut-être des hypothèses prématurées.N'essayez pas de tout simplifier. Il n'y a rien de plus décevant lorsque vous comptez sur quelque chose qui semble élégant et si innocent, pour réaliser que c'est la véritable horreur plus tard dans les pires moments.
la source
Ce que vous décrivez est une "exception" connue au principe de séparation commande-requête.
Dans cet article, Martin Fowler explique comment il menacerait une telle exception.
Dans votre exemple, je considérerais la même exception.
la source
La séparation commande-requête est incroyablement facile à comprendre.
Si je vous dis dans mon système il y a une commande qui retourne également une valeur à partir d'une requête et vous dites "Ha! Vous êtes en violation!" vous sautez le pistolet.
Non, ce n'est pas ce qui est interdit.
Ce qui est interdit, c'est quand cette commande est le SEUL moyen de faire cette requête. Non. Je ne devrais pas avoir à changer d'état pour poser des questions. Cela ne signifie pas que je dois fermer les yeux chaque fois que je change d'état.
Peu importe si je le fais car je vais juste les rouvrir de toute façon. Le seul avantage de cette réaction excessive était de garantir que les concepteurs ne soient pas paresseux et oublient d'inclure la requête sans changement d'état.
Le vrai sens est si sacrément subtil qu'il est juste plus facile pour les paresseux de dire que les colons devraient revenir sans effet. Cela permet de s'assurer plus facilement que vous ne violez pas la vraie règle, mais c'est une réaction excessive qui peut devenir une vraie douleur.
Les interfaces fluides et les iDSL "violent" cette réaction excessive tout le temps. Il y a beaucoup de pouvoir ignoré si vous réagissez trop.
Vous pouvez faire valoir qu'une commande ne doit faire qu'une seule chose et qu'une requête ne doit faire qu'une seule chose. Et dans de nombreux cas, c'est un bon point. Qui peut dire qu'il n'y a qu'une seule requête qui devrait suivre une commande? Très bien, mais ce n'est pas la séparation commande-requête. C'est le principe de la responsabilité unique.
Vu sous cet angle, un stack pop n'est pas une exception bizarre. C'est une seule responsabilité. Pop ne viole la séparation des requêtes de commande que si vous oubliez de fournir un aperçu.
la source