Ich geriet in eine interessante Internet-Auseinandersetzung über Getter- und Setter-Methoden und Verkapselung. Jemand sagte, dass alles, was sie tun sollten, eine Zuweisung (Setter) oder ein variabler Zugriff (Getter) sei, um sie "rein" zu halten und die Kapselung sicherzustellen.
- 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?
- Wann sollte die Validierung erfolgen?
- Beim Setzen des Wertes innerhalb des Setters (um das Objekt davor zu schützen, jemals in einen ungültigen Zustand zu gelangen - meine Meinung)
- Vor dem Einstellen des Wertes außerhalb des Einstellers
- Innerhalb des Objekts vor jeder Verwendung des Werts
- Darf ein Setter den Wert ändern (möglicherweise einen gültigen Wert in eine kanonische interne Darstellung umwandeln)?
object-oriented
language-agnostic
encapsulation
Botond Balázs
quelle
quelle
Antworten:
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:
und
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:
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:
Sagt dies etwas darüber aus, was mit dem Wert geschehen wird, wenn er dem Setter übergeben wird?
Wie wäre es mit:
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.
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.
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.
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.
quelle
Wenn der Getter / Setter den Wert einfach spiegelt, hat es keinen Sinn, sie zu haben oder den Wert privat zu machen. Es ist nichts Falsches daran, einige Mitgliedsvariablen öffentlich zu machen, wenn Sie einen guten Grund haben. Wenn Sie eine 3D-Punkteklasse schreiben, ist es absolut sinnvoll, .x, .y, .z public zu haben.
Wie Ralph Waldo Emerson sagte: "Eine dumme Konsequenz ist der Hobgoblin kleiner Köpfe, der von kleinen Staatsmännern und Philosophen und den Designern von Java verehrt wird."
Getter / Setter sind nützlich, wenn es zu Nebenwirkungen kommen kann, wenn Sie andere interne Variablen aktualisieren, zwischengespeicherte Werte neu berechnen und die Klasse vor ungültigen Eingaben schützen müssen.
Die übliche Rechtfertigung dafür, dass sie die innere Struktur verbergen, ist im Allgemeinen am wenigsten nützlich. z.B. Ich habe diese Punkte als 3 Floats gespeichert. Vielleicht entscheide ich mich, sie als Zeichenfolgen in einer entfernten Datenbank zu speichern, damit Getter / Setter sie verbergen, als ob Sie dies tun könnten, ohne den Code des Anrufers zu beeinflussen.
quelle
IEnumerable<T>
als sie zu erzwingenList<T>
. Ich würde sagen, Ihr Beispiel für den Datenbankzugriff verstößt gegen die Einzelverantwortung - indem Sie die Darstellung des Modells mit der Art und Weise mischen, wie es beibehalten wird.Meyers Prinzip des einheitlichen Zugriffs: "Alle von einem Modul angebotenen Dienste sollten durch eine einheitliche Notation verfügbar sein, die nicht verrät, ob sie durch Speicherung oder durch Berechnung implementiert werden." ist der Hauptgrund für die Getter / Setter, auch bekannt als Properties.
Wenn Sie ein Feld einer Klasse zwischenspeichern oder verzögert berechnen möchten, können Sie dies jederzeit ändern, wenn Sie nur Zugriffsmethoden für Eigenschaften und keine konkreten Daten verfügbar gemacht haben.
Wertobjekte, einfache Strukturen brauchen diese Abstraktion nicht, aber meiner Meinung nach eine vollwertige Klasse.
quelle
Eine gängige Strategie zum Entwerfen von Klassen, die über die Eiffel-Sprache eingeführt wurde, ist die Trennung von Befehlen und Abfragen . Die Idee ist, dass eine Methode entweder etwas über das Objekt aussagt oder das Objekt anweist, etwas zu tun, aber nicht beides.
Dies betrifft nur die öffentliche Schnittstelle der Klasse, nicht die interne Repräsentation. Stellen Sie sich ein Datenmodellobjekt vor, das durch eine Zeile in der Datenbank gesichert ist. Sie können das Objekt erstellen, ohne die Daten zu laden. Wenn Sie dann zum ersten Mal einen Getter aufrufen, führt es die Aktion aus
SELECT
. Das ist in Ordnung, Sie ändern möglicherweise einige interne Details zur Darstellung des Objekts, aber Sie ändern nicht, wie es für Clients dieses Objekts angezeigt wird. Sie sollten in der Lage sein, Getters mehrmals anzurufen und trotzdem die gleichen Ergebnisse zu erhalten, auch wenn sie unterschiedliche Arbeiten ausführen, um diese Ergebnisse zurückzugeben.In ähnlicher Weise sieht ein Setter vertraglich so aus, als würde er nur den Zustand eines Objekts ändern. Dies kann auf verschlungene Weise geschehen: Schreiben einer
UPDATE
in eine Datenbank oder Weitergabe des Parameters an ein internes Objekt. Das ist in Ordnung, aber etwas zu tun, das nichts mit dem Zustand zu tun hat, wäre überraschend.Meyer (der Schöpfer von Eiffel) hatte auch etwas über die Validierung zu sagen. Grundsätzlich sollte sich ein Objekt in einem gültigen Zustand befinden, wenn es sich im Ruhezustand befindet. Kurz nachdem der Konstruktor beendet wurde, vor und nach (aber nicht unbedingt während) jedem externen Methodenaufruf, sollte der Status des Objekts konsistent sein.
Es ist interessant festzustellen, dass in dieser Sprache die Syntax zum Aufrufen einer Prozedur und zum Lesen eines exponierten Attributs gleich aussieht. Mit anderen Worten, ein Aufrufer kann nicht erkennen, ob er eine Methode verwendet oder direkt mit einer Instanzvariablen arbeitet. Nur in Sprachen, die diese Implementierungsdetails nicht verbergen, wo die Frage überhaupt auftaucht - wenn Anrufer dies nicht feststellen konnten, konnten Sie zwischen einem öffentlichen Ivar und Zugriffsmethoden wechseln, ohne diese Änderung in Clientcode zu übernehmen.
quelle
Eine andere akzeptable Sache ist das Klonen. In einigen Fällen müssen Sie sicherstellen, dass, nachdem jemand Ihre Klasse gibt, z. eine Liste von etwas, er kann es nicht in Ihrer Klasse ändern (noch das Objekt in dieser Liste ändern). Daher erstellen Sie eine tiefe Kopie des Parameters im Setter und geben eine tiefe Kopie im Getter zurück. (Die Verwendung unveränderlicher Typen als Parameter ist eine weitere Option, setzt jedoch voraus, dass dies nicht möglich ist.) Klonen Sie Accessoren jedoch nicht, wenn sie nicht benötigt werden. Es ist leicht zu denken (aber nicht angemessen), dass Immobilien / Getter und Setter konstante Kosten verursachen. Dies ist also eine Performance-Landmine, die auf den Benutzer von API wartet.
quelle
Es gibt nicht immer eine 1: 1-Zuordnung zwischen Eigenschaftszugriffsberechtigten und den Ivars, in denen die Daten gespeichert sind.
Beispielsweise kann eine Ansichtsklasse eine
center
Eigenschaft bereitstellen , obwohl kein Ivar die Mitte der Ansicht speichert. Die Einstellungcenter
bewirkt Änderungen an anderen Ivars, wieorigin
odertransform
was auch immer, aber Clients der Klasse wissen nicht oder kümmern sich nicht darum, wie siecenter
gespeichert werden, vorausgesetzt, dass sie korrekt funktionieren. Was jedoch nicht passieren sollte, ist, dass die Einstellungcenter
dazu führt, dass Dinge geschehen, die über das hinausgehen, was zum Speichern des neuen Werts erforderlich ist.quelle
Das Beste an Setter und Gettern ist, dass Sie die Regeln einer API auf einfache Weise ändern können, ohne die API zu ändern. Wenn Sie einen Fehler entdecken, ist es viel wahrscheinlicher, dass Sie den Fehler in der Bibliothek beheben können und nicht jeder Konsument seine Codebasis aktualisieren muss.
quelle
Ich neige dazu zu glauben, dass Setter und Getter böse sind und nur in Klassen verwendet werden sollten, die von einem Framework / Container verwaltet werden. Ein ordentliches Klassendesign sollte keine Stärken und Stärken bringen.
Edit: ein gut geschriebener Artikel zu diesem Thema .
Edit2: öffentliche Felder sind ein Unsinn in einem OOP-Ansatz; Mit der Aussage, dass Getter und Setter böse sind, meine ich nicht, dass sie durch öffentliche Felder ersetzt werden sollten.
quelle