Ich stoße oft auf dieses Problem, besonders in Java, auch wenn ich denke, dass es sich um ein allgemeines OOP-Problem handelt. Das heißt: Das Auslösen einer Ausnahme zeigt ein Konstruktionsproblem auf.
Angenommen, ich habe eine Klasse mit einem String name
Feld und einem String surname
Feld.
In diesen Feldern wird dann der vollständige Name einer Person angegeben, um ihn auf einem Dokument, beispielsweise einer Rechnung, anzuzeigen.
public void String name;
public void String surname;
public String getCompleteName() {return name + " " + surname;}
public void displayCompleteNameOnInvoice() {
String completeName = getCompleteName();
//do something with it....
}
Jetzt möchte ich das Verhalten meiner Klasse stärken, indem ich einen Fehler auslöse, wenn der displayCompleteNameOnInvoice
aufgerufen wird, bevor der Name vergeben wurde. Es scheint eine gute Idee zu sein, nicht wahr?
Ich kann der getCompleteName
Methode einen Code hinzufügen, der Ausnahmen auslöst . Auf diese Weise verstoße ich jedoch gegen einen "impliziten" Vertrag mit dem Klassenbenutzer. Im Allgemeinen dürfen Getter keine Ausnahmen auslösen, wenn ihre Werte nicht festgelegt sind. Ok, dies ist kein Standard-Getter, da kein einzelnes Feld zurückgegeben wird, aber aus Sicht des Benutzers ist die Unterscheidung möglicherweise zu subtil, um darüber nachzudenken.
Oder ich kann die Ausnahme von innen werfen displayCompleteNameOnInvoice
. Aber dazu sollte ich direkt testen name
oder surname
Felder und damit würde ich die durch dargestellte Abstraktion verletzen getCompleteName
. Es liegt in der Verantwortung dieser Methode, den vollständigen Namen zu überprüfen und zu erstellen. Es könnte sogar auf der Grundlage anderer Daten entscheiden, dass es in einigen Fällen ausreicht, die surname
.
So die einzige Möglichkeit scheint die semantischen des Verfahrens zu ändern , getCompleteName
zu composeCompleteName
, dass ein ‚aktives‘ legt nahe , Verhalten und damit auch die Fähigkeit , eine Ausnahme zu werfen.
Ist das die bessere Designlösung? Ich bin immer auf der Suche nach der besten Balance zwischen Einfachheit und Korrektheit. Gibt es eine Designreferenz für dieses Problem?
quelle
displayCompleteNameOnInvoice
können Sie einfach eine Ausnahme auslösen, wenn siegetCompleteName
zurückgegeben wirdnull
, nicht wahr ?Antworten:
Lassen Sie nicht zu, dass Ihre Klasse ohne Zuweisung eines Namens erstellt wird.
quelle
Die von DeadMG bereitgestellte Antwort nagelt es ziemlich genau, aber lassen Sie es mich etwas anders formulieren: Vermeiden Sie Objekte mit ungültigem Status. Ein Objekt sollte im Kontext der Aufgabe, die es erfüllt, "ganz" sein. Oder es sollte nicht existieren.
Wenn Sie also Fluidität wünschen, verwenden Sie so etwas wie das Builder-Muster . Oder irgendetwas anderes, das eine separate Verifizierung eines Objekts in der Konstruktion im Gegensatz zu der "realen Sache" ist, wobei letzterer garantiert einen gültigen Zustand hat und derjenige ist, der tatsächlich die für diesen Zustand definierten Operationen verfügbar macht (z
getCompleteName
. B. ).quelle
So if you want fluidity, use something like the builder pattern
- Fluidität oder Unveränderlichkeit (es sei denn, Sie meinen das irgendwie). Wenn Sie unveränderliche Objekte erstellen möchten, aber das Festlegen einiger Werte verschieben müssen, müssen Sie diese nacheinander für ein Builder-Objekt festlegen und erst dann ein unveränderliches Objekt erstellen, wenn Sie alle für das richtige Objekt erforderlichen Werte erfasst haben Zustand ...Kein Objekt mit ungültigem Status.
Der Zweck eines Konstruktors oder Generators besteht darin, den Status des Objekts auf etwas Konsistentes und Verwendbares festzulegen. Ohne diese Garantie treten bei Objekten Probleme mit einem nicht ordnungsgemäß initialisierten Zustand auf. Dieses Problem tritt auf, wenn Sie sich mit Parallelität befassen und etwas auf das Objekt zugreifen kann, bevor Sie es vollständig eingerichtet haben.
Die Frage für diesen Teil lautet also: "Warum lässt du zu, dass der Name oder der Nachname null ist?" Wenn dies in der Klasse nicht gültig ist, damit es ordnungsgemäß funktioniert, lassen Sie es nicht zu. Verfügen Sie über einen Konstruktor oder Builder, der die Erstellung des Objekts ordnungsgemäß überprüft, und werfen Sie das Problem an diesem Punkt auf, wenn es nicht richtig ist. Möglicherweise möchten Sie auch eine der vorhandenen
@NotNull
Anmerkungen verwenden , um mitzuteilen, dass "dies nicht null sein kann", und sie in Codierung und Analyse durchzusetzen.Mit dieser Garantie wird es viel einfacher, über den Code nachzudenken, was er tut, und keine Ausnahmen an ungewöhnlichen Stellen auszulösen oder übermäßige Überprüfungen der Getter-Funktionen vorzunehmen.
Getter, die mehr können.
Es gibt eine Menge zu diesem Thema. Du hast wie viel Logik in Getter und Was soll innerhalb Getter und Setter erlaubt sein? von hier und Gettern und Setzern, die zusätzliche Logik für Stack Overflow ausführen . Dies ist ein Thema, das im Klassendesign immer wieder auftaucht.
Der Kern davon kommt von:
Und du hast recht. Sie sollten nicht. Sie sollten für den Rest der Welt "dumm" aussehen und das tun, was erwartet wird. Zu viel Logik führt zu Problemen, bei denen das Gesetz des geringsten Erstaunens verletzt wird. Und seien
try catch
wir ehrlich, Sie möchten die Getter wirklich nicht mit einem einwickeln, weil wir wissen, wie sehr wir das im Allgemeinen lieben.Es gibt auch Situationen , in denen Sie müssen ein verwenden
getFoo
Verfahren, wie die Java Beans Rahmen und wenn Sie etwas von haben EL erwartet Aufruf eine Bohne zu bekommen (so dass<% bar.foo %>
tatsächlich AnrufegetFoo()
in der Klasse - abgesehen von der ‚soll die Bohne die Zusammensetzung tun oder sollte ist das der sicht überlassen? 'weil man leicht situationen finden kann, in denen das eine oder andere eindeutig die richtige antwort sein kann)Beachten Sie auch, dass es möglich ist, dass ein bestimmter Getter in einer Schnittstelle angegeben wird oder Teil der zuvor offengelegten öffentlichen API für die Klasse ist, die umgestaltet wird (eine frühere Version hatte nur 'completeName', das zurückgegeben wurde, und ein Umgestaltungsfehler es in zwei Felder).
Am Ende des Tages...
Machen Sie das, worüber Sie am einfachsten nachdenken können. Sie verbringen mehr Zeit mit der Pflege dieses Codes als mit dem Entwerfen (je weniger Zeit Sie mit dem Entwerfen verbringen, desto mehr Zeit verbringen Sie mit der Pflege). Die ideale Wahl besteht darin, das Design so zu gestalten, dass die Wartung nicht so lange dauert, aber auch nicht tagelang darüber nachgedacht wird - es sei denn, es handelt sich tatsächlich um ein Design, das sich erst Tage später auswirken wird .
Der Versuch, sich an die semantische Reinheit des Getter-Wesens zu halten,
private T foo; T getFoo() { return foo; }
wird Sie irgendwann in Schwierigkeiten bringen. Manchmal passt der Code einfach nicht zu diesem Modell und die Verzerrungen, die man durchläuft, um ihn so zu halten, ergeben einfach keinen Sinn ... und erschweren letztendlich das Entwerfen.Akzeptiere manchmal, dass die Ideale des Designs nicht so umgesetzt werden können, wie du es im Code willst. Richtlinien sind Richtlinien - keine Fesseln.
quelle
surname
odername
Null ist ein gültiger Zustand für das Objekt, dann behandeln Sie es entsprechend. Wenn es sich jedoch nicht um einen gültigen Status handelt, der dazu führt, dass Methoden in der Klasse Ausnahmen auslösen, sollte verhindert werden, dass sie sich ab dem Zeitpunkt der Initialisierung in diesem Status befinden. Sie können unvollständige Objekte weitergeben, aber es ist problematisch, ungültige Objekte weiterzugeben. Wenn ein ungültiger Nachname eine Ausnahme darstellt, versuchen Sie, ihn früher als später zu verhindern.Ich bin kein Java-Typ, aber dies scheint sich an beide Einschränkungen zu halten, die Sie vorgestellt haben.
getCompleteName
Löst keine Ausnahme aus, wenn die Namen nicht initialisiert sinddisplayCompleteNameOnInvoice
.quelle
getCompleteName()
nicht darum kümmern, ob die Eigenschaft tatsächlich gespeichert oder im laufenden Betrieb berechnet wird.getCompleteName
, was abscheulich ist.getCompleteName()
in der restlichen Klasse oder sogar in anderen Teilen des Programms gibt. In einigen Fällen kann die Rücksendungnull
genau das sein, was erforderlich ist. Aber während es EINE Methode gibt, deren Spezifikation "in diesem Fall eine Ausnahme auslösen" lautet, sollten wir genau das codieren.Es scheint, als würde niemand Ihre Frage beantworten.
Im Gegensatz zu dem, was die Leute gerne glauben, ist "ungültige Objekte vermeiden" oft keine praktische Lösung.
Die Antwort ist:
Ja das sollte es.
Einfaches Beispiel:
FileStream.Length
in C # /. NET , das wirft,NotSupportedException
wenn die Datei nicht suchbar ist.quelle
Sie haben die ganze Sache mit den überprüfenden Namen auf den Kopf gestellt.
getCompleteName()
muss nicht wissen, welche Funktionen es verwenden werden. Somit ist es nicht in der Verantwortung ,name
beim AnrufendisplayCompleteNameOnInvoice()
keinen zu habengetCompleteName()
.Aber
name
ist eine klare Voraussetzung fürdisplayCompleteNameOnInvoice()
. Daher sollte es die Verantwortung für die Überprüfung des Status übernehmen (oder es sollte die Verantwortung für die Überprüfung an eine andere Methode delegieren , wenn Sie dies vorziehen).quelle