Accessoren und Modifikatoren (auch Setter und Getter genannt) sind aus drei Hauptgründen nützlich:
- Sie schränken den Zugriff auf die Variablen ein.
- Zum Beispiel kann auf eine Variable zugegriffen, aber nicht geändert werden.
- Sie validieren die Parameter.
- Sie können einige Nebenwirkungen verursachen.
Universitäten, Online-Kurse, Tutorials, Blog-Artikel und Codebeispiele im Internet betonen die Wichtigkeit der Zugriffs- und Modifizierungsfunktionen. Sie fühlen sich heutzutage fast wie ein "Muss" für den Code an. Man kann sie also auch dann finden, wenn sie keinen zusätzlichen Wert liefern, wie im folgenden Code.
public class Cat {
private int age;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
Es ist jedoch üblich, weitere nützliche Modifikatoren zu finden, die die Parameter tatsächlich validieren und eine Ausnahme auslösen oder einen Booleschen Wert zurückgeben, wenn ungültige Eingaben eingegeben wurden.
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
Aber selbst dann sehe ich fast nie, dass die Modifikatoren von einem Konstruktor aufgerufen wurden. Das häufigste Beispiel für eine einfache Klasse, mit der ich konfrontiert bin, ist das folgende:
public class Cat {
private int age;
public Cat(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
}
Aber man würde denken, dass dieser zweite Ansatz viel sicherer ist:
public class Cat {
private int age;
public Cat(int age) {
//Use the modifier instead of assigning the value directly.
setAge(age);
}
public int getAge() {
return this.age;
}
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
}
Sehen Sie ein ähnliches Muster in Ihrer Erfahrung oder bin es nur ich, der Pech hat? Und wenn ja, was ist Ihrer Meinung nach der Grund dafür? Gibt es einen offensichtlichen Nachteil bei der Verwendung von Modifikatoren der Konstrukteure oder werden sie nur als sicherer angesehen? Ist es etwas anderes
quelle
Antworten:
Sehr allgemeines philosophisches Denken
In der Regel bitten wir einen Konstruktor (als Nachbedingung) um einige Garantien für den Zustand des konstruierten Objekts.
In der Regel gehen wir auch davon aus, dass Instanzmethoden (als Vorbedingungen) davon ausgehen können, dass diese Garantien bereits gelten, wenn sie aufgerufen werden, und dass sie nur sicherstellen müssen, dass sie nicht zerstört werden.
Das Aufrufen einer Instanzmethode aus dem Konstruktor heraus bedeutet, dass einige oder alle dieser Garantien möglicherweise noch nicht festgelegt wurden. Daher ist es schwierig zu beurteilen, ob die Voraussetzungen für die Instanzmethode erfüllt sind. Selbst wenn Sie es richtig machen, kann es sehr zerbrechlich sein, z. Neuanordnen von Instanzmethodenaufrufen oder anderen Vorgängen.
Sprachen unterscheiden sich auch darin, wie sie Aufrufe an Instanzmethoden auflösen, die von Basisklassen geerbt / von Unterklassen überschrieben werden, während der Konstruktor noch ausgeführt wird. Dies fügt eine weitere Komplexitätsebene hinzu.
Spezifische Beispiele
Ihr eigenes Beispiel dafür, wie dies Ihrer Meinung nach aussehen sollte, ist an sich falsch:
Dies prüft nicht den Rückgabewert von
setAge
. Offensichtlich ist ein Anruf beim Einrichter doch keine Garantie für die Richtigkeit.Sehr einfache Fehler, wie abhängig von der Initialisierungsreihenfolge, wie zum Beispiel:
wo meine vorübergehende Protokollierung alles brach. Hoppla!
Es gibt auch Sprachen wie C ++, in denen das Aufrufen eines Setters aus dem Konstruktor eine verschwendete Standardinitialisierung bedeutet (was zumindest für einige Mitgliedsvariablen zu vermeiden ist).
Ein einfacher Vorschlag
Es ist wahr, dass der meiste Code nicht so geschrieben ist, aber wenn Sie Ihren Konstruktor sauber und vorhersehbar halten und trotzdem Ihre Vor- und Nachbedingungslogik wiederverwenden möchten, ist die bessere Lösung:
oder noch besser, wenn möglich: Codieren Sie die Einschränkung im Eigenschaftstyp und lassen Sie sie ihren eigenen Wert bei der Zuweisung validieren:
Und schließlich der Vollständigkeit halber ein selbstvalidierender Wrapper in C ++. Beachten Sie, dass die Überprüfung relativ einfach ist , obwohl sie immer noch mühsam ist, da diese Klasse nichts anderes tut
OK, es ist nicht wirklich vollständig, ich habe verschiedene Kopier- und Verschiebungskonstruktoren und -zuweisungen weggelassen.
quelle
Wenn ein Objekt konstruiert wird, ist es per Definition nicht vollständig ausgebildet. Überlegen Sie, wie der von Ihnen angegebene Setter:
Was wäre, wenn ein Teil der Validierung
setAge()
eine Überprüfung derage
Immobilie beinhalten würde, um sicherzustellen, dass das Objekt nur im Alter zunehmen könnte? Diese Bedingung könnte folgendermaßen aussehen:Das scheint eine ziemlich unschuldige Veränderung zu sein, da Katzen sich nur in einer Richtung durch die Zeit bewegen können. Das einzige Problem ist, dass Sie damit nicht den Anfangswert von festlegen können,
this.age
da dies vorausgesetzt wirdthis.age
bereits ein gültiger Wert vorliegt.Das Vermeiden von Setzern in Konstruktoren macht deutlich, dass der Konstruktor nur die Instanzvariable setzt und jedes andere Verhalten, das im Setzer auftritt, überspringt.
quelle
Einige gute Antworten hier bereits, aber bisher schien niemand bemerkt zu haben, dass Sie hier zwei Dinge in einem fragen, was einige Verwirrung stiftet. IMHO wird Ihr Beitrag am besten als zwei separate Fragen gesehen:
Wenn eine Einschränkung in einem Setter validiert wird, sollte sie sich nicht auch im Konstruktor der Klasse befinden, um sicherzustellen, dass sie nicht umgangen werden kann?
warum nicht den setter dazu direkt vom konstruktor aus aufrufen?
Die Antwort auf die ersten Fragen lautet eindeutig "Ja". Ein Konstruktor sollte, wenn er keine Ausnahme auslöst, das Objekt in einem gültigen Zustand belassen, und wenn bestimmte Werte für bestimmte Attribute verboten sind, ist es absolut sinnlos, den ctor dies umgehen zu lassen.
Die Antwort auf die zweite Frage lautet jedoch in der Regel "nein", solange Sie keine Maßnahmen ergreifen, um zu vermeiden, dass der Setter in einer abgeleiteten Klasse überschrieben wird. Daher besteht die bessere Alternative darin, die Gültigkeitsprüfung für Einschränkungen in einer privaten Methode zu implementieren, die sowohl vom Setter als auch vom Konstruktor aus wiederverwendet werden kann. Ich werde hier nicht das Beispiel @Useless wiederholen, das genau diesen Entwurf zeigt.
quelle
Hier ist ein blöder Java-Code, der die Art von Problemen demonstriert, mit denen Sie in Ihrem Konstruktor mit nicht endgültigen Methoden konfrontiert werden können:
Wenn ich dies ausführe, erhalte ich eine NPE im NextProblem-Konstruktor. Dies ist natürlich ein triviales Beispiel, aber es kann schnell kompliziert werden, wenn Sie mehrere Vererbungsebenen haben.
Ich denke, der größere Grund, warum dies nicht üblich wurde, ist, dass es den Code ein bisschen schwieriger macht, ihn zu verstehen. Persönlich habe ich fast nie Setter-Methoden und meine Mitgliedsvariablen sind fast immer (Wortspiel beabsichtigt) endgültig. Aus diesem Grund müssen die Werte im Konstruktor festgelegt werden. Wenn Sie unveränderliche Objekte verwenden (und es gibt viele gute Gründe dafür), ist die Frage umstritten.
In jedem Fall ist es ein gutes Ziel, die Validierung oder andere Logik wiederzuverwenden, und Sie können sie in eine statische Methode einfügen und sie sowohl vom Konstruktor als auch vom Setter aus aufrufen.
quelle
name
Feld nicht gesetzt wird).this
-Referenz nicht an eine andere Methode übergeben, da Sie noch nicht sicher sind, ob sie vollständig initialisiert ist.Es hängt davon ab, ob!
Wenn Sie eine einfache Überprüfung durchführen oder im Setter unerwünschte Nebenwirkungen auslösen, die bei jedem Festlegen der Eigenschaft auftreten müssen: Verwenden Sie den Setter. Die Alternative besteht im Allgemeinen darin, die Setterlogik aus dem Setter zu extrahieren und die neue Methode aufzurufen, gefolgt von der tatsächlichen Menge sowohl vom Setter als auch vom Setter vom Konstruktor. (Nur dass Sie jetzt Ihren zugegebenermaßen kleinen Setter mit zwei Zeilen an zwei Stellen warten.)
Allerdings , wenn Sie komplexe Operationen ausführen , um das Objekt anzuordnen , bevor eine bestimmte Setter (oder zwei!) Konnte erfolgreich ausgeführt werden , nicht die Setter verwenden.
Und in jedem Fall Testfälle haben . Unabhängig davon, auf welcher Route Sie sich befinden, haben Sie bei Unit-Tests eine gute Chance, die mit beiden Optionen verbundenen Fallstricke aufzudecken.
Ich füge hinzu, wenn mein Gedächtnis von damals auch nur annähernd korrekt ist, ist dies die Art von Argumentation, die mir auch am College beigebracht wurde. Und es steht in keiner Weise im Widerspruch zu erfolgreichen Projekten, an denen ich arbeiten durfte.
Wenn ich raten müsste, hätte ich irgendwann ein Memo mit "sauberem Code" verpasst, in dem einige typische Fehler identifiziert wurden, die bei der Verwendung von Setzern in einem Konstruktor auftreten können. Aber ich für meinen Teil bin noch keinem solchen Fehler zum Opfer gefallen ...
Ich würde also argumentieren, dass diese spezielle Entscheidung nicht pauschal und dogmatisch sein muss.
quelle