Dieser Blogpost wurde in den Hacker News mit mehreren positiven Stimmen gepostet. Aus C ++ stammend, scheinen die meisten dieser Beispiele gegen das zu verstoßen, was mir beigebracht wurde.
Zum Beispiel # 2:
Schlecht:
def check_for_overheating(system_monitor)
if system_monitor.temperature > 100
system_monitor.sound_alarms
end
end
gegen gut:
system_monitor.check_for_overheating
class SystemMonitor
def check_for_overheating
if temperature > 100
sound_alarms
end
end
end
Der Rat in C ++ ist, dass Sie freie Funktionen anstelle von Member-Funktionen bevorzugen sollten, da diese die Kapselung erhöhen. Beide sind semantisch identisch. Warum also die Wahl bevorzugen, die Zugriff auf mehr Status hat?
Beispiel 4:
Schlecht:
def street_name(user)
if user.address
user.address.street_name
else
'No street name on file'
end
end
gegen gut:
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
Warum liegt es in der Verantwortung User
, eine nicht verwandte Fehlerzeichenfolge zu formatieren? Was ist, wenn ich etwas anderes als Drucken machen möchte, 'No street name on file'
wenn es keine Straße gibt? Was ist, wenn die Straße den gleichen Namen hat?
Könnte mich jemand über die Vorteile und Gründe von "Tell, Don't Ask" aufklären? Ich suche nicht, was besser ist, sondern versuche, den Standpunkt des Autors zu verstehen.
quelle
Antworten:
Wenn Sie das Objekt nach seinem Status fragen und dann Methoden für dieses Objekt aufrufen, die auf Entscheidungen außerhalb des Objekts basieren, bedeutet dies, dass das Objekt jetzt eine undichte Abstraktion ist. Ein Teil seines Verhaltens befindet sich außerhalb des Objekts, und der interne Zustand ist (möglicherweise unnötigerweise) der Außenwelt ausgesetzt.
http://pragprog.com/articles/tell-dont-ask
quelle
Im Allgemeinen schlägt das Stück vor, dass Sie den Mitgliedsstaat nicht anderen zur Diskussion stellen sollten, wenn Sie selbst darüber urteilen könnten .
Unklar ist jedoch, dass dieses Gesetz in ganz offensichtliche Grenzen stößt, wenn die Argumentation weit über der Verantwortung einer bestimmten Klasse liegt. Zum Beispiel jede Klasse, deren Aufgabe es ist, einen Wert zu halten oder einen Wert bereitzustellen - insbesondere einen generischen - oder bei der die Klasse ein Verhalten bereitstellt, das erweitert werden muss.
Wenn beispielsweise das System das
temperature
als Abfrage bereitstellt , dann kann der Client morgencheck_for_underheating
ohne ÄnderungSystemMonitor
. Dies ist nicht der Fall, wenn dasSystemMonitor
Gerätcheck_for_overheating
selbst arbeitet.SystemMonitor
Dem folgt also eine Klasse, deren Aufgabe es ist, einen Alarm auszulösen, wennSystemMonitor
die Temperatur zu hoch ist - aber eine Klasse, deren Aufgabe es ist, einem anderen Teil des Codes das Lesen der Temperatur zu ermöglichen, damit er beispielsweise TurboBoost oder ähnliches steuern kann , sollte nicht.Beachten Sie auch, dass das zweite Beispiel sinnlos das Null-Objekt-Anti-Muster verwendet.
quelle
check_for_underheating
ohne sich ändern zu müssenSystemMonitor
" verstehe . Wie unterscheidet sich der KundeSystemMonitor
zu diesem Zeitpunkt? Verteilen Sie Ihre Überwachungslogik jetzt nicht auf mehrere Klassen? Ich sehe auch kein Problem mit einer Monitorklasse, die andere Klassen mit Sensorinformationen versorgt und gleichzeitig die Alarmfunktionen für sich selbst reserviert. Der Boost-Regler sollte den Boost regeln, ohne sich darum kümmern zu müssen, einen Alarm auszulösen, wenn die Temperatur zu hoch wird.Das eigentliche Problem bei Ihrem Überhitzungsbeispiel besteht darin, dass die Regeln für das, was als Überhitzung bezeichnet wird, für verschiedene Systeme nicht leicht geändert werden können. Angenommen, System A ist so, wie Sie es haben (Temp.> 100 ist überhitzt), aber System B ist empfindlicher (Temp.> 93 ist überhitzt). Ändern Sie Ihre Steuerfunktion, um den Systemtyp zu überprüfen und dann den richtigen Wert anzuwenden?
Oder hat jeder Anlagentyp seine Heizleistung definiert?
BEARBEITEN:
Bei der ersten Methode wird Ihre Steuerungsfunktion hässlich, wenn Sie anfangen, sich mit mehr Systemen zu befassen. Letzteres sorgt dafür, dass die Steuerfunktion im Laufe der Zeit stabil bleibt.
quelle
Zunächst einmal bin ich der Meinung, dass ich Ihrer Charakterisierung der Beispiele als "schlecht" und "gut" eine Ausnahme machen muss. In dem Artikel werden die Begriffe "Nicht so gut" und "Besser" verwendet. Ich denke, diese Begriffe wurden aus einem Grund ausgewählt: Dies sind Richtlinien, und je nach den Umständen kann der Ansatz "Nicht so gut" angemessen oder die einzige Lösung sein.
Wenn Sie die Wahl haben, sollten Sie es vorziehen , Funktionen, die ausschließlich von der Klasse abhängen, in die Klasse einzubeziehen, anstatt außerhalb der Klasse. Der Grund hierfür liegt in der Kapselung und der Tatsache, dass die Entwicklung der Klasse im Laufe der Zeit einfacher wird. Die Klasse macht auch bessere Werbung für ihre Fähigkeiten als eine Reihe von kostenlosen Funktionen.
Manchmal muss man es sagen, weil die Entscheidung von etwas außerhalb der Klasse abhängt oder weil es einfach etwas ist, das die meisten Benutzer der Klasse nicht wollen. Manchmal möchten Sie dies mitteilen, da das Verhalten für die Klasse nicht intuitiv ist und Sie die meisten Benutzer der Klasse nicht verwirren möchten.
Zum Beispiel beschweren Sie sich, dass die Straße eine Fehlermeldung zurückgibt. Dies ist nicht der Fall, sondern die Angabe eines Standardwerts. Aber manchmal ist ein Standardwert nicht angemessen. Wenn dies Bundesstaat oder Stadt war, möchten Sie möglicherweise eine Standardeinstellung, wenn Sie einem Verkäufer oder einem Umfrageteilnehmer einen Datensatz zuweisen, damit alle Unbekannten an eine bestimmte Person weitergeleitet werden. Wenn Sie dagegen Umschläge drucken, bevorzugen Sie möglicherweise eine Ausnahme oder einen Schutz, der Sie davon abhält, Papier für Briefe zu verschwenden, die nicht zugestellt werden können.
Es kann also Fälle geben, in denen "Nicht so gut" der richtige Weg ist, aber im Allgemeinen "Besser" besser ist.
quelle
Daten / Objekt-Antisymmetrie
Wie bereits erwähnt, ist Tell-Dont-Ask speziell für Fälle gedacht, in denen Sie den Objektstatus nach Ihrer Anfrage ändern (siehe z. B. den Pragprog-Text an einer anderen Stelle auf dieser Seite). Dies ist nicht immer der Fall, z. B. wird das Benutzerobjekt nicht geändert, nachdem es nach seiner Benutzeradresse gefragt wurde. Es ist daher fraglich, ob dies ein angemessener Fall ist, um Tell-Dont-Ask anzuwenden.
Tell-Dont-Ask befasst sich mit der Verantwortung, keine Logik aus einer Klasse herauszuholen, die zu Recht darin enthalten sein sollte. Aber nicht jede Logik, die sich mit Objekten befasst, ist notwendigerweise die Logik dieser Objekte. Dies deutet auf eine tiefere Ebene hin, sogar über Tell-Dont-Ask hinaus, und ich möchte eine kurze Bemerkung dazu hinzufügen.
Im Hinblick auf die architektonische Gestaltung möchten Sie möglicherweise Objekte haben, die eigentlich nur Container für Eigenschaften sind, möglicherweise sogar unveränderlich, und dann verschiedene Funktionen über Sammlungen solcher Objekte ausführen, diese auswerten, filtern oder transformieren, anstatt ihnen Befehle zu senden (d. H mehr die Domain von Tell-Dont-Ask).
Die Entscheidung, die für Ihr Problem besser geeignet ist, hängt davon ab, ob Sie stabile Daten (deklarative Objekte) erwarten, diese aber auf der Funktionsseite ändern / hinzufügen. Oder wenn Sie einen stabilen und eingeschränkten Satz solcher Funktionen erwarten, aber mehr Fluss auf Objektebene erwarten, z. B. durch Hinzufügen neuer Typen. In der ersten Situation würden Sie freie Funktionen bevorzugen, in der zweiten Objektmethode.
Bob Martin nennt dies in seinem Buch "Clean Code" "Data / Object Anti-Symmetry" (S. 95 ff.), Andere Communities könnten es als " Ausdrucksproblem " bezeichnen.
quelle
Dieses Paradigma wird manchmal als "Sagen, nicht fragen" bezeichnet , was bedeutet, dem Objekt zu sagen, was zu tun ist, und nicht nach seinem Zustand zu fragen. und manchmal als "Fragen, nicht erzählen" , was bedeutet, das Objekt zu bitten, etwas für Sie zu tun, es nicht zu sagen, wie sein Zustand sein sollte. Die beste Vorgehensweise ist in beiden Fällen gleich: Die Art und Weise, in der ein Objekt eine Aktion ausführen soll, betrifft das Objekt und nicht das aufrufende Objekt. Schnittstellen sollten vermeiden, ihren Status offenzulegen (z. B. über Accessoren oder öffentliche Eigenschaften), und stattdessen "Doing" -Methoden offenlegen, deren Implementierung undurchsichtig ist. Andere haben dies mit den Links zu pragmatischen Programmierern abgedeckt.
Diese Regel von der Regel verwendet ist über „Doppelpunkt“ oder „Doppelpfeil“ Code, die oft als "nur reden sofortige Freunde zu vermeiden, in dem es heißt
foo->getBar()->doSomething()
schlecht ist , verwenden Sie stattdessenfoo->doSomething();
die ein Wrapper Anruf um Bar Funktionalität ist, und einfach umgesetztreturn bar->doSomething();
- wennfoo
man für das Management verantwortlich istbar
, dann lass es tun!quelle
Zusätzlich zu den anderen guten Antworten zu "Tell, Don't Ask", einige Kommentare zu Ihren spezifischen Beispielen, die hilfreich sein könnten:
Diese Wahl hat keinen Zugang zu mehr Staat. Beide verwenden dieselbe Menge an Staat, um ihre Arbeit zu erledigen, aber das „schlechte“ Beispiel verlangt, dass der Klassenstaat öffentlich ist, um seine Arbeit zu erledigen. Darüber hinaus wird das Verhalten dieser Klasse im "schlechten" Beispiel auf die freie Funktion ausgedehnt, was es schwieriger macht, sie zu finden und schwieriger umzugestalten.
Warum liegt es in der Verantwortung von 'street_name', sowohl 'get street name' als auch 'supply error message' auszuführen? Zumindest in der "guten" Version hat jedes Stück eine Verantwortung. Trotzdem ist es kein gutes Beispiel.
quelle
Diese Antworten sind sehr gut, aber hier ist ein weiteres Beispiel, um es zu betonen: Beachten Sie, dass es normalerweise eine Möglichkeit ist, Doppelarbeit zu vermeiden. Nehmen wir zum Beispiel an, Sie haben MEHRERE Stellen mit einem Code wie:
Das heißt, du solltest besser so etwas haben:
Da diese Duplizierung bedeutet, dass die meisten Clients Ihrer Schnittstelle die neue Methode verwenden, anstatt hier und da dieselbe Logik zu wiederholen. Sie geben Ihrem Delegierten die Arbeit, die Sie erledigen möchten, anstatt nach den Informationen zu fragen, die Sie benötigen, um sie selbst zu erledigen.
quelle
Ich glaube, dass dies beim Schreiben von Objekten auf hoher Ebene wahrer ist, aber weniger, wenn man in die tiefere Ebene, z. B. die Klassenbibliothek, geht, da es unmöglich ist, jede einzelne Methode zu schreiben, um alle Klassenkonsumenten zufriedenzustellen.
Zum Beispiel # 2 denke ich, dass es zu stark vereinfacht ist. Wenn wir dies tatsächlich implementieren würden, würde der SystemMonitor den Code für den Hardwarezugriff auf niedriger Ebene und die Logik für die Abstraktion auf hoher Ebene in dieselbe Klasse einbetten. Wenn wir versuchen, dies in zwei Klassen zu unterteilen, verstoßen wir leider gegen das "Tell, Don't Ask" -Prinzip.
Das Beispiel Nr. 4 ist mehr oder weniger dasselbe - es bettet die UI-Logik in die Datenschicht ein. Wenn wir nun korrigieren möchten, was der Benutzer sehen möchte, wenn keine Adresse vorhanden ist, müssen wir das Objekt in der Datenebene korrigieren. Und wenn zwei Projekte dasselbe Objekt verwenden, aber unterschiedlichen Text für die Nulladresse verwenden müssen?
Ich bin damit einverstanden, dass wenn wir "Tell, Don't Ask" für alles implementieren können, es sehr nützlich wäre - ich selbst würde mich freuen, wenn ich im wirklichen Leben nur erzählen kann, anstatt zu fragen (und es selbst zu tun)! Wie im echten Leben ist die Machbarkeit der Lösung jedoch stark auf High-Level-Klassen beschränkt.
quelle