Das Prinzip Tell Don't Ask besagt:
Sie sollten versuchen, Objekten mitzuteilen, was sie tun sollen. Fragen Sie sie nicht nach ihrem Zustand, treffen Sie keine Entscheidung und sagen Sie ihnen dann, was sie tun sollen.
Das Problem ist, dass Sie als Aufrufer keine Entscheidungen treffen sollten, die auf dem Status des aufgerufenen Objekts basieren und zu einer Änderung des Status des Objekts führen. Die Logik, die Sie implementieren, liegt wahrscheinlich in der Verantwortung des aufgerufenen Objekts, nicht in Ihrer. Wenn Sie Entscheidungen außerhalb des Objekts treffen, wird dessen Kapselung verletzt.
Ein einfaches Beispiel für "Tell, Don't Ask" ist
Widget w = ...;
if (w.getParent() != null) {
Panel parent = w.getParent();
parent.remove(w);
}
und die tell version ist ...
Widget w = ...;
w.removeFromParent();
Was ist, wenn ich das Ergebnis der removeFromParent-Methode kennen muss? Meine erste Reaktion bestand lediglich darin, removeFromParent so zu ändern, dass ein Boolescher Wert zurückgegeben wird, der angibt, ob das übergeordnete Element entfernt wurde oder nicht.
Aber dann bin ich auf das Trennmuster für Befehlsabfragen gestoßen, das besagt, dass dies NICHT erfolgen soll.
Jede Methode sollte entweder ein Befehl sein, der eine Aktion ausführt, oder eine Abfrage, die Daten an den Aufrufer zurückgibt, jedoch nicht beide. Mit anderen Worten, das Stellen einer Frage sollte die Antwort nicht ändern. Formal sollten Methoden nur dann einen Wert zurückgeben, wenn sie referenziell transparent sind und daher keine Nebenwirkungen aufweisen.
Sind diese beiden wirklich uneins und wie wähle ich zwischen den beiden? Gehe ich mit dem Pragmatic Programmer oder Bertrand Meyer darüber?
Antworten:
Tatsächlich zeigt Ihr Beispielproblem bereits einen Mangel an Zersetzung.
Lass es uns einfach ein bisschen ändern:
Das ist nicht wirklich anders, macht aber den Fehler deutlicher: Warum sollte das Buch über sein Regal Bescheid wissen? Einfach gesagt, sollte es nicht. Es wird eine Abhängigkeit von Büchern in Regalen eingeführt (was keinen Sinn ergibt) und Zirkelverweise erstellt. Es ist alles schlecht.
Ebenso ist es nicht erforderlich, dass ein Widget sein übergeordnetes Element kennt. Sie werden sagen: "Ok, aber das Widget benötigt seine Eltern, um sich selbst richtig zu gestalten usw." und unter der Haube des Widget kennt seine Eltern und fragt sie für ihre Metriken seine eigenen Metriken zu berechnen , basierend auf sie usw. Nach sagen, nicht fragen , das ist falsch. Das übergeordnete Element sollte allen untergeordneten Elementen das Rendern anweisen und alle erforderlichen Informationen als Argumente übergeben. Auf diese Weise kann man leicht dasselbe Widget in zwei Elternteilen haben (unabhängig davon, ob dies tatsächlich Sinn macht oder nicht).
Um zum Buchbeispiel zurückzukehren, geben Sie den Bibliothekar ein:
Bitte haben Sie Verständnis, dass wir nicht fragen , mehr im Sinne von tell, fragen Sie nicht .
In der ersten Version war das Regal Eigentum des Buches und Sie haben danach gefragt. In der zweiten Version machen wir keine solche Annahme über das Buch. Wir wissen nur, dass der Bibliothekar uns ein Regal mit dem Buch mitteilen kann. Vermutlich verlässt er sich dazu auf eine Art Nachschlagetabelle, aber wir wissen es nicht genau (er könnte auch einfach alle Bücher in jedem Regal durchgehen) und eigentlich wollen wir es nicht wissen .
Wir verlassen uns nicht darauf, ob oder wie die Reaktion der Bibliothekare an ihren Zustand oder den anderer Objekte gekoppelt ist, von denen sie abhängig sein könnten. Wir bitten den Bibliothekar, das Regal zu finden.
Im ersten Szenario haben wir die Beziehung zwischen einem Buch und einem Regal als Teil des Buchstatus direkt codiert und diesen Status direkt abgerufen. Dies ist sehr fragil, da wir auch davon ausgehen, dass das vom Buch zurückgegebene Regal das Buch enthält (dies ist eine Einschränkung, die wir sicherstellen müssen, da wir sonst möglicherweise nicht in
remove
der Lage sind, das Buch aus dem Regal zu entnehmen, in dem es sich tatsächlich befindet).Mit der Einführung des Bibliothekars modellieren wir diese Beziehung separat und erreichen so eine Trennung der Anliegen.
quelle
Wenn Sie das Ergebnis wissen müssen, dann tun Sie es; Das ist Ihre Anforderung.
Der Ausdruck "Methoden sollten nur dann einen Wert zurückgeben, wenn sie referenziell transparent sind und daher keine Nebenwirkungen aufweisen" ist eine gute Richtlinie (insbesondere, wenn Sie Ihr Programm in einem funktionalen Stil schreiben , aus Parallelitätsgründen oder aus anderen Gründen) kein absolutes.
Möglicherweise müssen Sie das Ergebnis eines Dateischreibvorgangs kennen (true oder false). Dies ist ein Beispiel für eine Methode, die einen Wert zurückgibt, jedoch immer Nebenwirkungen hervorruft. Daran führt kein Weg vorbei.
Um die Befehls- / Abfragetrennung zu erfüllen, müssen Sie die Dateioperation mit einer Methode ausführen und anschließend das Ergebnis mit einer anderen Methode überprüfen. Dies ist eine unerwünschte Technik, da das Ergebnis von der Methode entkoppelt wird, die das Ergebnis verursacht hat. Was passiert, wenn zwischen dem Dateiaufruf und der Statusprüfung etwas mit dem Status Ihres Objekts passiert?
Die Quintessenz lautet: Wenn Sie das Ergebnis eines Methodenaufrufs verwenden , um an anderer Stelle im Programm Entscheidungen zu treffen, verletzen Sie Tell Don't Ask nicht. Wenn Sie andererseits Entscheidungen für ein Objekt auf der Grundlage eines Methodenaufrufs für dieses Objekt treffen, sollten Sie diese Entscheidungen in das Objekt selbst verschieben , um die Kapselung beizubehalten.
Dies steht in keinerlei Widerspruch zur Befehlsabfragetrennung. Tatsächlich wird es weiter erzwungen, da keine externe Methode mehr für Statuszwecke verfügbar gemacht werden muss.
quelle
Ich denke, dass Sie mit Ihrem anfänglichen Instinkt gehen sollten. Manchmal sollte das Design absichtlich gefährlich sein und sofort Komplexität erzeugen, wenn Sie mit dem Objekt kommunizieren, sodass Sie es sofort richtig handhaben müssen, anstatt zu versuchen, es am Objekt selbst zu handhaben, alle blutigen Details zu verbergen und es schwierig zu machen, es sauber zu halten die zugrunde liegenden Annahmen ändern.
Sie sollten ein sinkendes Gefühl bekommen, wenn ein Objekt Ihnen implementierte Äquivalente
open
undclose
Verhaltensweisen bietet und Sie sofort beginnen zu verstehen, womit Sie es zu tun haben, wenn Sie einen booleschen Rückgabewert für etwas sehen, von dem Sie dachten, dass es eine einfache atomare Aufgabe wäre.Wie Sie später damit umgehen, ist Ihre Sache. Sie können darüber eine Abstraktion erstellen, einen Widget-Typ mit
removeFromParent()
, aber Sie sollten immer einen Low-Level-Fallback haben, da Sie sonst möglicherweise verfrühte Annahmen getroffen haben.Versuche nicht alles einfach zu machen. Es gibt nichts Enttäuschenderes, als sich auf etwas zu verlassen, das elegant und unschuldig erscheint, um später im schlimmsten Moment zu erkennen, dass es wahrer Horror ist.
quelle
Was Sie beschreiben, ist eine bekannte "Ausnahme" des Befehlsabfragetrennungsprinzips.
In diesem Artikel erklärt Martin Fowler , wie er eine solche Ausnahme bedrohen würde.
In Ihrem Beispiel würde ich die gleiche Ausnahme betrachten.
quelle
Die Trennung von Befehlen und Abfragen ist erstaunlich leicht zu missverstehen.
Wenn ich Ihnen in meinem System sage, dass es einen Befehl gibt, der auch einen Wert aus einer Abfrage zurückgibt, und Sie sagen: "Ha! Sie sind verletzt!" Du springst die Waffe.
Nein, das ist nicht verboten.
Was verboten ist, ist, wenn dieser Befehl die EINZIGE Möglichkeit ist, diese Abfrage durchzuführen. Nein, ich sollte nicht den Status ändern müssen, um Fragen zu stellen. Das bedeutet nicht, dass ich meine Augen jedes Mal schließen muss, wenn ich den Zustand ändere.
Ist mir egal, da ich sie sowieso einfach wieder öffne. Der einzige Vorteil dieser Überreaktion war, dass die Designer nicht faul wurden und die Abfrage, die den Status nicht ändert, nicht vergessen.
Die wahre Bedeutung ist so subtil, dass es für faule Leute einfacher ist, zu sagen, Setter sollten für ungültig erklären. Dies macht es einfacher, sicherzugehen, dass Sie nicht gegen die wirkliche Regel verstoßen, aber es ist eine Überreaktion, die zu einem echten Schmerz werden kann.
Fließende Schnittstellen und iDSLs "verletzen" diese Überreaktion die ganze Zeit. Es wird viel Kraft ignoriert, wenn Sie überreagieren.
Sie können argumentieren, dass ein Befehl nur eine Sache tun sollte und eine Abfrage nur eine Sache tun sollte. Und in vielen Fällen ist das ein guter Punkt. Wer sagt, dass es immer nur eine Abfrage gibt, die einem Befehl folgen sollte? Gut, aber das ist keine Trennung von Befehlen und Abfragen. Das ist das Prinzip der Einzelverantwortung.
So gesehen ist ein Stack-Pop keine bizarre Ausnahme. Es ist eine einzelne Verantwortung. Pop verletzt die Trennung von Befehlsabfragen nur, wenn Sie vergessen, peek anzugeben.
quelle