Cet article a été publié dans Hacker News avec plusieurs votes positifs. Venant du C ++, la plupart de ces exemples semblent aller à l’encontre de ce que j’ai appris.
Tels que l'exemple n ° 2:
Mauvais:
def check_for_overheating(system_monitor)
if system_monitor.temperature > 100
system_monitor.sound_alarms
end
end
contre bon:
system_monitor.check_for_overheating
class SystemMonitor
def check_for_overheating
if temperature > 100
sound_alarms
end
end
end
Le conseil en C ++ est que vous devriez préférer les fonctions libres aux fonctions membres car elles augmentent l'encapsulation. Ces deux éléments sont sémantiquement identiques, alors pourquoi préférer le choix qui a accès à plus d’état?
Exemple 4:
Mauvais:
def street_name(user)
if user.address
user.address.street_name
else
'No street name on file'
end
end
contre bon:
def street_name(user)
user.address.street_name
end
class User
def address
@address || NullAddress.new
end
end
class NullAddress
def street_name
'No street name on file'
end
end
Pourquoi est-il de la responsabilité de User
formater une chaîne d'erreur sans rapport? Que faire si je veux faire autre chose que d’imprimer 'No street name on file'
s’il n’ya pas de rue? Et si la rue porte le même nom?
Est-ce que quelqu'un pourrait m'éclairer sur les avantages et la raison d'être du programme «Dites, ne demandez pas»? Je ne cherche pas ce qui est préférable, mais j'essaie plutôt de comprendre le point de vue de l'auteur.
la source
Réponses:
Interroger l'objet sur son état, puis appeler des méthodes sur cet objet en fonction de décisions prises en dehors de l'objet, signifie que l'objet est maintenant une abstraction qui fuit; une partie de son comportement est située à l' extérieur de l'objet et l'état interne est exposé (peut-être inutilement) au monde extérieur.
http://pragprog.com/articles/tell-dont-ask
la source
En règle générale, l'article suggère que vous ne devriez pas exposer les États membres aux autres, si vous pouviez les raisonner vous-même .
Cependant, ce qui n’est pas clairement indiqué, c’est que cette loi se heurte à des limites très évidentes lorsque le raisonnement dépasse largement la responsabilité d’une classe donnée. Par exemple, chaque classe dont le travail consiste à conserver une valeur ou à en fournir une, en particulier les valeurs génériques, ou lorsque la classe fournit un comportement qui doit être étendu.
Par exemple, si le système fournit le
temperature
comme une requête, demain, le client peutcheck_for_underheating
sans avoir à changerSystemMonitor
. Ce n’est pas le cas lorsque l’SystemMonitor
implémentationcheck_for_overheating
elle-même. Ainsi, uneSystemMonitor
classe dont le travail est de déclencher une alarme lorsque la température est trop élevée le suit, mais uneSystemMonitor
classe dont le travail est de permettre à un autre morceau de code de lire la température afin de pouvoir contrôler, par exemple, TurboBoost ou quelque chose comme ça. , ne devrait pas.Notez également que le deuxième exemple utilise inutilement l'Anti-pattern Null Object.
la source
check_for_underheating
sans avoir à changerSystemMonitor
". En quoi le client est-il différent deSystemMonitor
ce moment-là? Ne dissipez-vous pas maintenant votre logique de surveillance sur plusieurs classes? Je ne vois pas non plus le problème avec une classe de moniteur qui fournit des informations de capteur à d'autres classes tout en réservant des fonctions d'alarme à elle-même. Le contrôleur de suralimentation doit contrôler l’alimentation sans avoir à s’inquiéter de la possibilité de déclencher une alarme si la température est trop élevée.Le véritable problème de votre exemple de surchauffe est que les règles relatives à ce qui est qualifié de surchauffe ne sont pas facilement modifiables pour différents systèmes. Supposons que le système A soit tel que vous l'avez (temp> 100 surchauffe) mais le système B est plus délicat (temp> 93 surchauffe). Modifiez-vous votre fonction de contrôle pour vérifier le type de système, puis appliquez la valeur correcte?
Ou avez-vous chaque type de système définir sa capacité de chauffage?
MODIFIER:
La première façon de rendre votre fonction de contrôle devient laide lorsque vous commencez à traiter avec plus de systèmes. Ce dernier permet de stabiliser la fonction de contrôle au fil du temps.
la source
Tout d’abord, j’ai le sentiment que je dois admettre que vous qualifiez les exemples de «mauvais» et de «bon». L'article utilise les termes "Pas si bon" et "Meilleur", je pense que ces termes ont été choisis pour une raison: ce sont des lignes directrices, et selon les circonstances, l'approche "Pas si bon" peut être appropriée, voire la seule solution.
Lorsque vous avez le choix, vous devriez préférer inclure toute fonctionnalité qui repose uniquement sur la classe dans la classe plutôt qu'en dehors de celle-ci. La raison en est l'encapsulation et le fait qu'il est plus facile de faire évoluer la classe au fil du temps. La classe fait également un meilleur travail de publicité pour ses capacités qu'un tas de fonctions gratuites.
Parfois, il faut dire, parce que la décision repose sur quelque chose d'extérieur à la classe ou simplement parce que c'est quelque chose que vous ne voulez pas que la plupart des utilisateurs de la classe fassent. Parfois, vous voulez savoir, car le comportement est contre-intuitif pour la classe et vous ne voulez pas dérouter la plupart des utilisateurs de la classe.
Par exemple, vous vous plaignez du fait que l'adresse postale renvoie un message d'erreur. Ce n'est pas le cas, elle fournit une valeur par défaut. Mais parfois, une valeur par défaut n'est pas appropriée. S'il s'agissait d'un État ou d'une ville, vous souhaiterez peut-être définir une valeur par défaut lors de l'attribution d'un enregistrement à un vendeur ou à un répondant, de manière à ce que tous les inconnus soient attribués à une personne spécifique. Par contre, si vous imprimiez des enveloppes, vous préféreriez peut-être une exception ou une protection qui vous évite de gaspiller du papier avec des lettres qui ne peuvent pas être livrées.
Il peut donc y avoir des cas où "Pas si bon" est la voie à suivre, mais en général, "Mieux" est, eh bien, meilleur.
la source
Anti-symétrie données / objets
Comme d'autres l'ont souligné, Tell-Dont-Ask est spécifiquement destiné aux cas où vous modifiez l'état de l'objet après avoir demandé (voir par exemple le texte de Pragprog posté ailleurs sur cette page). Ce n'est pas toujours le cas, par exemple, l'objet 'utilisateur' n'est pas modifié après avoir été invité à indiquer son adresse utilisateur. C'est donc discutable s'il s'agit d'un cas approprié pour appliquer Tell-Dont-Ask.
Tell-Dont-Ask se préoccupe de responsabilité, de ne pas extraire la logique d’une classe qui devrait y figurer à juste titre. Mais toute la logique qui traite des objets n'est pas nécessairement la logique de ces objets. Ceci suggère un niveau plus profond, même au-delà de Tell-Dont-Ask, et je voudrais ajouter une brève remarque à ce sujet.
En ce qui concerne la conception architecturale, vous souhaiterez peut-être utiliser des objets qui ne sont en réalité que des conteneurs de propriétés, voire immuables, puis exécuter diverses fonctions sur des collections de tels objets, en les évaluant, en les filtrant ou en les transformant plutôt qu'en leur envoyant des commandes plus le domaine de Tell-Dont-Ask).
La décision qui convient le mieux à votre problème dépend de si vous souhaitez disposer de données stables (les objets déclaratifs) mais de la modification / l'ajout du côté de la fonction. Ou si vous vous attendez à avoir un ensemble stable et limité de telles fonctions mais attendez-vous à plus de flux au niveau des objets, par exemple en ajoutant de nouveaux types. Dans le premier cas, vous préféreriez des fonctions libres, dans le second méthodes d'objet.
Bob Martin, dans son livre "Clean Code", appelle cela "l'Anti-Symétrie Données / Objets" (p.95ff), d'autres communautés pourraient l'appeler le " problème d'expression ".
la source
Ce paradigme est parfois appelé «Dis, ne demande pas» , ce qui signifie dire à l'objet quoi faire, ne demande pas son état; et parfois comme "Demandez, ne dites pas" , ce qui signifie demander à l'objet de faire quelque chose pour vous, ne lui dites pas quel devrait être son état. Quoi qu'il en soit, la meilleure pratique est la même: la manière dont un objet doit exécuter une action est son intérêt, pas celui de l'appelant. Les interfaces doivent éviter d'exposer leur état (par exemple via des accesseurs ou des propriétés publiques) et exposer plutôt des méthodes de type «faire» dont la mise en œuvre est opaque. D'autres ont couvert cela avec les liens vers les programmeurs pragmatiques.
Cette règle est liée à la règle permettant d'éviter le code "à double point" ou "à double flèche", souvent appelée "Ne parler qu'à des amis immédiats", qui indique que
foo->getBar()->doSomething()
c'est mauvais, utilisez plutôtfoo->doSomething();
un appel encapsulant les fonctionnalités de barre, et mis en œuvre simplementreturn bar->doSomething();
- s’ilfoo
est responsable de la gestionbar
, alors laissez-le faire!la source
En plus des autres bonnes réponses sur "Dites, ne demandez pas", quelques commentaires sur vos exemples spécifiques qui pourraient aider:
Ce choix n'a pas accès à plus d'état. Ils utilisent tous les deux la même quantité d’État pour faire leur travail, mais le «mauvais» exemple exige que l’État de classe soit public pour pouvoir faire son travail. De plus, le comportement de cette classe dans le "mauvais" exemple est étendu à la fonction libre, ce qui le rend plus difficile à trouver et plus difficile à refactoriser.
Pourquoi est-ce la responsabilité de 'nom de rue' de faire à la fois 'obtenir le nom de la rue' et 'de fournir un message d'erreur'? Au moins dans la «bonne» version, chaque pièce a une responsabilité. Pourtant, ce n'est pas un bon exemple.
la source
Ces réponses sont très bonnes, mais voici un autre exemple à souligner: notez que c'est généralement un moyen d'éviter les doubles emplois. Par exemple, supposons que vous ayez plusieurs endroits avec un code tel que:
Cela signifie que vous feriez mieux d'avoir quelque chose comme ça:
Parce que cette duplication signifie que la plupart des clients de votre interface utiliseraient la nouvelle méthode, au lieu de répéter la même logique ici et là. Vous donnez à votre délégué le travail que vous voulez faire, au lieu de demander les informations dont vous avez besoin pour le faire vous-même.
la source
Je crois que cela est plus vrai lors de l'écriture d'un objet de haut niveau, mais moins vrai lorsque vous descendez au niveau plus profond, par exemple une bibliothèque de classes, car il est impossible d'écrire chaque méthode pour satisfaire tous les consommateurs de classe.
Par exemple # 2, je pense que c'est trop simplifié. Si nous allions réellement implémenter cela, SystemMonitor finirait par avoir le code pour un accès matériel de bas niveau et une logique pour une abstraction de haut niveau intégrée dans la même classe. Malheureusement, si nous essayons de séparer cela en deux classes, nous violerions le "Dis, ne demande pas" lui-même.
L'exemple n ° 4 est plus ou moins identique: il intègre la logique de l'interface utilisateur au niveau des données. Maintenant, si nous voulons corriger ce que l'utilisateur veut voir en cas d'absence d'adresse, nous devons corriger l'objet dans la couche de données, et que se passe-t-il si deux projets utilisant le même objet mais doivent utiliser un texte différent pour une adresse nulle?
Je conviens que si nous pouvions mettre en œuvre «Dis, ne demande pas» pour tout, ce serait très utile - je serais moi-même heureux si je pouvais juste dire plutôt que de demander (et de le faire moi-même) dans la vraie vie! Toutefois, comme dans la vie réelle, la faisabilité de la solution est très limitée aux classes de haut niveau.
la source