Was sollte in Gettern und Setzern erlaubt sein?


45

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)?

18
Das Beste an Gettern und Setzern ist die Fähigkeit, sie nicht zu haben , dh das Verlassen des Setters und Sie haben eine Nur-Lese-Eigenschaft, das Verlassen des Getters und Sie haben eine Konfigurationsoption, deren aktueller Wert niemand interessiert.
Michael Borgwardt

7
@MichaelBorgwardt Lassen Sie beide weg, um eine saubere Benutzeroberfläche zu haben. Eigenschaften = potentieller Code-Geruch.
Konrad Rudolph


2
Setter können auch zum Auslösen eines Ereignisses verwendet werden .
John Isaiah Carmona

@KonradRudolph Ich stimme Ihrer Aussage zu, obwohl ich das Wort "Potenzial" wirklich betonen möchte.
Phil

Antworten:


37

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.


Bei Änderung des Werts kann es sinnvoll sein, den Wert auf -1 oder ein NULL-Flag-Token zu setzen, wenn dem Setter ein unzulässiger Wert übergeben wird
Martin Beckett,

1
Es gibt ein paar Probleme damit. Das willkürliche Einstellen eines Wertes erzeugt eine absichtliche und unklare Nebenwirkung. Außerdem kann der aufrufende Code kein Feedback erhalten, das zur besseren Behandlung illegaler Daten verwendet werden könnte. Dies ist besonders wichtig bei Werten auf Benutzeroberflächenebene. Um fair zu sein, eine Ausnahme, an die ich gerade gedacht habe, könnte sein, dass mehrere Datumsformate als Eingabe zulässig sind, während das Datum in einem Standardformat für Datum und Uhrzeit gespeichert wird. Man könnte durchaus argumentieren, dass dieser spezielle "Nebeneffekt" eine Normalisierung der Daten bei der Validierung ist, vorausgesetzt, die Eingabedaten sind legal.
S.Robins

Ja, eine der Begründungen eines Setters ist, dass er true / false zurückgeben sollte, wenn der Wert festgelegt werden konnte.
Martin Beckett

1
"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." Unter welchen Umständen löst zu viele Dezimalstellen eine Ausnahme aus?
Mark Byers

2
Ich sehe das Ändern des Wertes in eine kanonische Darstellung als eine Form der Validierung. Das Standardisieren fehlender Werte (z. B. des aktuellen Datums, wenn ein Zeitstempel nur die Uhrzeit enthält) oder das Skalieren eines Werts (.93275 -> 93,28%), um die interne Konsistenz sicherzustellen, sollte in Ordnung sein. Solche Manipulationen sollten jedoch ausdrücklich in der API-Dokumentation angegeben werden Dies gilt insbesondere dann, wenn sie in der API eine ungewöhnliche Sprache sind.
TMN

20

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.


1
Wow, die Designer von Java sind göttlich? </ jk>

@delnan - offensichtlich nicht - oder sie würden sich besser reimen ;-)
Martin Beckett

Wäre es gültig, einen 3D-Punkt zu erstellen, in dem x, y, z alle Float.NAN sind?
Andrew T Finnell

4
Ich stimme Ihrer Ansicht nicht zu, dass "die übliche Rechtfertigung" (das Verbergen der inneren Struktur) die am wenigsten nützliche Eigenschaft von Gettern und Setzern ist. In C # ist es auf jeden Fall nützlich, die Eigenschaft zu einer Schnittstelle zu machen, deren zugrunde liegende Struktur geändert werden kann. Wenn Sie beispielsweise eine Eigenschaft nur für die Aufzählung zur Verfügung haben möchten, ist es viel besser, dies zu sagen, IEnumerable<T>als sie zu erzwingen List<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.
Brandon Linton

@AndrewFinnell - könnte es eine gute Möglichkeit , das Markieren von Punkten als unmöglich / nicht gültig / gelöschter etc sein
Martin Beckett

8

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.


2

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 UPDATEin 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.


1

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.


1

Es gibt nicht immer eine 1: 1-Zuordnung zwischen Eigenschaftszugriffsberechtigten und den Ivars, in denen die Daten gespeichert sind.

Beispielsweise kann eine Ansichtsklasse eine centerEigenschaft bereitstellen , obwohl kein Ivar die Mitte der Ansicht speichert. Die Einstellung centerbewirkt Änderungen an anderen Ivars, wie originoder transformwas auch immer, aber Clients der Klasse wissen nicht oder kümmern sich nicht darum, wie sie center gespeichert werden, vorausgesetzt, dass sie korrekt funktionieren. Was jedoch nicht passieren sollte, ist, dass die Einstellung centerdazu führt, dass Dinge geschehen, die über das hinausgehen, was zum Speichern des neuen Werts erforderlich ist.


0

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.


-4

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.


1
Ich denke, Sie verpassen den Punkt des Artikels, der vorschlägt, Getter / Setter sparsam zu verwenden und Klassendaten nicht preiszugeben, sofern dies nicht erforderlich ist. Diese IMHO ist ein sinnvolles Gestaltungsprinzip, das vom Entwickler bewusst angewendet werden sollte. Im Hinblick auf die Schaffung von Eigenschaften für eine Klasse, Sie könnten einfach eine Variable aussetzen, aber das macht es schwieriger , die Validierung zu liefern , wenn die Variable gesetzt ist, oder den Wert aus einer anderen Quelle zu ziehen , wenn „got“, im Wesentlichen Kapselung zu verletzen und möglicherweise Sie in ein schwer zu pflegendes Interface-Design einbinden.
S.Robins

@ S.Robins Meiner Meinung nach sind öffentliche Getter und Setter falsch; Öffentliche Felder sind falscher (wenn das vergleichbar ist).
m3th0dman

@methodman Ja, ich stimme zu, dass ein öffentliches Feld falsch ist, jedoch kann eine öffentliche Eigenschaft nützlich sein. Diese Eigenschaft kann verwendet werden, um einen Ort für die Validierung oder Ereignisse im Zusammenhang mit der Einstellung oder Rückgabe der Daten bereitzustellen, je nach den jeweiligen Anforderungen. Getter und Setter selbst sind per se nicht falsch. Wie und wann sie andererseits benutzt oder missbraucht werden, kann in Bezug auf Design und Wartbarkeit je nach den Umständen als schlecht angesehen werden. :)
S.Robins

@ S.Robins Denken Sie über die Grundlagen von OOP und Modellierung nach. Objekte sollen reale Entitäten kopieren. Gibt es reale Entitäten, die diese Art von Operationen / Eigenschaften haben, wie z. B. Getter / Setter?
m3th0dman

Um zu vermeiden, dass hier zu viele Kommentare angezeigt werden, nehme ich diese Konversation in diesen Chat auf und spreche dort Ihren Kommentar an.
S.Robins
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.