Ich erinnere mich an eine ähnliche Auseinandersetzung mit meinem Dozenten, als ich an der Universität C ++ lernte. Ich konnte es einfach nicht verstehen, Getter und Setter zu verwenden, wenn ich eine Variable öffentlich machen konnte. Ich verstehe jetzt besser mit jahrelanger Erfahrung und habe einen besseren Grund gelernt, als einfach zu sagen "um die Kapselung aufrechtzuerhalten".
Durch die Definition der Getter und Setter stellen Sie eine konsistente Schnittstelle zur Verfügung, sodass abhängigen Code weniger leicht beschädigt wird, wenn Sie Ihre Implementierung ändern möchten. Dies ist besonders wichtig, wenn Ihre Klassen über eine API verfügbar gemacht und in anderen Apps oder von Drittanbietern verwendet werden. Also, was ist mit dem Zeug, das in den Getter oder Setter geht?
Getter werden in der Regel besser als einfaches Passthrough für den Zugriff auf einen Wert implementiert, da dadurch ihr Verhalten vorhersehbar wird. Ich sage allgemein, weil ich Fälle gesehen habe, in denen Getter verwendet wurden, um auf Werte zuzugreifen, die durch Berechnung oder sogar durch bedingten Code manipuliert wurden. Im Allgemeinen nicht so gut, wenn Sie visuelle Komponenten für die Verwendung zur Entwurfszeit erstellen, aber zur Laufzeit praktisch erscheinen. Es gibt jedoch keinen wirklichen Unterschied zu einer einfachen Methode, mit der Ausnahme, dass Sie bei der Verwendung einer Methode in der Regel eine Methode geeigneter benennen, sodass die Funktionalität des "Getter" beim Lesen des Codes deutlicher wird.
Vergleichen Sie Folgendes:
int aValue = MyClass.Value;
und
int aValue = MyClass.CalculateValue();
Die zweite Option macht deutlich, dass der Wert berechnet wird, während Sie im ersten Beispiel lediglich einen Wert zurückgeben, ohne etwas über den Wert selbst zu wissen.
Sie könnten vielleicht argumentieren, dass Folgendes klarer wäre:
int aValue = MyClass.CalculatedValue;
Das Problem ist jedoch, dass Sie davon ausgehen, dass der Wert bereits an einer anderen Stelle manipuliert wurde. Im Fall eines Getters ist es also schwierig, solche Dinge im Kontext einer Eigenschaft zu verdeutlichen, obwohl Sie vielleicht annehmen möchten, dass etwas anderes passiert, wenn Sie einen Wert zurückgeben, und Eigenschaftsnamen sollten niemals Verben enthalten andernfalls ist auf einen Blick schwer zu verstehen, ob der verwendete Name beim Zugriff mit Klammern versehen werden soll.
Setter sind jedoch ein etwas anderer Fall. Es ist völlig angemessen, dass ein Setter eine zusätzliche Verarbeitung bereitstellt, um die an eine Eigenschaft gesendeten Daten zu validieren und eine Ausnahme auszulösen, wenn das Setzen eines Werts die definierten Grenzen der Eigenschaft verletzen würde. Das Problem, das einige Entwickler haben, wenn sie Setter mit einer zusätzlichen Verarbeitung versehen, ist jedoch, dass der Setter immer versucht ist, etwas mehr zu tun, z. B. eine Berechnung oder eine Manipulation der Daten auf irgendeine Weise. Hier können Nebenwirkungen auftreten, die in einigen Fällen unvorhersehbar oder unerwünscht sein können.
Im Falle von Setzern wende ich immer eine einfache Faustregel an, die darin besteht, die Daten so wenig wie möglich zu bearbeiten. Zum Beispiel erlaube ich normalerweise entweder Grenzprüfungen und Rundungen, damit ich bei Bedarf beide Ausnahmen auslösen oder unnötige Ausnahmen vermeiden kann, bei denen sie sinnvoll vermieden werden können. Gleitkomma-Eigenschaften sind ein gutes Beispiel, bei dem Sie möglicherweise übermäßige Dezimalstellen runden möchten, um das Auslösen einer Ausnahme zu vermeiden, während Sie die Eingabe von In-Range-Werten mit einigen zusätzlichen Dezimalstellen zulassen.
Wenn Sie eine Art Manipulation der Setzeingabe vornehmen, haben Sie das gleiche Problem wie beim Getter: Es ist schwierig, anderen zu ermöglichen, zu wissen, was der Setter tut, indem Sie ihn einfach benennen. Zum Beispiel:
MyClass.Value = 12345;
Sagt dies etwas darüber aus, was mit dem Wert geschehen wird, wenn er dem Setter übergeben wird?
Wie wäre es mit:
MyClass.RoundValueToNearestThousand(12345);
Das zweite Beispiel sagt Ihnen genau, was mit Ihren Daten geschehen wird, während das erste Sie nicht informiert, wenn Ihr Wert willkürlich geändert wird. Beim Lesen von Code wird das zweite Beispiel in Zweck und Funktion viel deutlicher.
Habe ich recht, dass dies den Zweck, Getter und Setter zu haben, völlig zunichte machen würde und Validierung und andere Logik (natürlich ohne seltsame Nebenwirkungen) zugelassen werden sollten?
Bei Gettern und Setzern geht es nicht um die Verkapselung aus Gründen der "Reinheit", sondern um die Verkapselung, damit Code leicht umgestaltet werden kann, ohne das Risiko einer Änderung der Schnittstelle der Klasse, die andernfalls die Kompatibilität der Klasse mit aufrufendem Code beeinträchtigen würde. Die Validierung ist in einem Setter durchaus angemessen, es besteht jedoch ein geringes Risiko, dass eine Änderung der Validierung die Kompatibilität mit dem aufrufenden Code beeinträchtigt, wenn der aufrufende Code auf eine bestimmte Art und Weise von der Validierung abhängt. Dies ist eine im Allgemeinen seltene und relativ risikoarme Situation. Der Vollständigkeit halber sei jedoch darauf hingewiesen.
Wann sollte die Validierung erfolgen?
Die Validierung sollte im Kontext des Setters erfolgen, bevor der Wert tatsächlich festgelegt wird. Dadurch wird sichergestellt, dass sich der Status Ihres Objekts nicht ändert und möglicherweise seine Daten ungültig werden, wenn eine Ausnahme ausgelöst wird. Im Allgemeinen finde ich es besser, die Validierung an eine separate Methode zu delegieren, die als Erstes im Setter aufgerufen wird, um den Setter-Code relativ übersichtlich zu halten.
Darf ein Setter den Wert ändern (möglicherweise einen gültigen Wert in eine kanonische interne Darstellung umwandeln)?
In sehr seltenen Fällen vielleicht. Im Allgemeinen ist es wahrscheinlich besser, dies nicht zu tun. So etwas überlässt man am besten einer anderen Methode.