Wenn eine Variable Getter und Setter hat, sollte sie öffentlich sein?


23

Ich habe eine Klasse mit einer Variablen, die privat ist, und die Klasse hat einen Getter und einen Setter für diese Variable. Warum diese Variable nicht öffentlich machen?

Der einzige Fall, den ich denke, dass Sie Getter und Setter verwenden müssen, ist, wenn Sie eine Operation neben dem Set oder dem Get ausführen müssen. Beispiel:

void my_class::set_variable(int x){
   /* Some operation like updating a log */
   this->variable = x;
}

10
Getter und Setter dienen der Abschirmung. Möglicherweise möchten Sie eines Tages this->variable = x + 5eine UpdateStatisticsFunktion im Setter ausführen oder aufrufen , was in diesen Fällen classinstancea->variable = 5zu Problemen führt.
Coder

1
Ein weiteres Beispiel ist: set_inch, set_centimeter, get_inch, get_centimeter, mit einigen gruseligen Aktionen.
Rwong

Ja, Getter und Setter helfen Ihnen dabei, Ihre Instanzvariablen so zu steuern, wie Sie es wünschen.
Developerdoug

7
@Coder: kannst du jederzeit deinen getter / setter hinzufügen wenn das nötig ist? Oder ist dies etwas, was Sie in C ++ nicht leicht tun können (meine Erfahrung ist hauptsächlich Delphi)?
Marjan Venema

Diese Frage: programmers.stackexchange.com/questions/21802/… könnte von Interesse sein.
Winston Ewert

Antworten:


21

Haben Sie jemals von einer Immobilie gehört?

Eine Eigenschaft ist ein Feld mit "eingebauten" Zugriffsmechanismen (Gettern und Setzern). Java zum Beispiel hat keine Eigenschaften, aber es wird empfohlen, die Getter und Setter in ein privates Feld zu schreiben. C # hat Eigenschaften.

Also, warum brauchen wir Getter und Setter? Grundsätzlich brauchen wir es, um das Feld zu schützen / abzuschirmen . Wenn Sie beispielsweise nicht auf das Feld in der Speicherreferenz zugreifen, greifen Sie auf eine Methode zu, mit der das Feld (die Referenz) geändert wird. Diese Methode ist in der Lage, einige Vorgänge auszuführen, die ein Benutzer nicht wissen möchte ( Verkapselung von Verhalten ), wie in Ihrem Beispiel. Stellen Sie sich zum Beispiel vor, dass ein Dutzend Klassen Ihr öffentliches Feld verwenden und Sie die Art und Weise ändern müssen, wie es verwendet wird. Sie müssten sich jede dieser Klassen ansehen, um die Art und Weise zu ändern, in der sie das Feld verwenden also "OOlysh".

Aber zum Beispiel, wenn Sie ein boolesches Feld haben, das als tot bezeichnet wird. Sie sollten zweimal überlegen, bevor Sie setDead und isDead deklarieren. Sie sollten für Menschen lesbare Accessoren schreiben , z. B. kill () anstelle von setDead.

Es gibt jedoch viele Frameworks, die davon ausgehen, dass Sie die JavaBean-Namenskonvention befolgen (hier wird über Java gesprochen). In diesen Fällen sollten Sie daher alle Get- und Setter deklarieren, die der Namenskonvektion folgen.


2
Jetzt ist "menschlich lesbar" endlich ein Argument, das ich "grocken" kann. Alle anderen Argumente, die ich normalerweise höre, sind eine Art Zukunftssicherung, die genauso leicht angegangen werden kann, wenn Bedarf besteht ...
Marjan Venema

13
Ihre Verachtung für "Zukunftssicherheit" ist weit verbreitet, aber falsch. Ja, Sie können auf solche Änderungen bei Bedarf eingehen, wenn Sie der einzige Kunde dieses Codes sind . Wenn Sie Ihre Lösung nie für externe Kunden veröffentlichen, können Sie einfach technische Schulden machen, und niemand muss es jemals wissen. Aber interne Projekte haben die Angewohnheit, öffentlich zu werden, wenn Sie es am wenigsten erwarten, und dann wehe Ihnen.
Kilian Foth

2
Sie sprechen tatsächlich einen fantastischen Punkt an. kill ist kein set / get, es ist eine action. Die Implementierung wird vollständig ausgeblendet - so codieren Sie OO. Eigenschaften hingegen sind genauso dumm wie Setter / Getter - dumm, auch weil die einfache Syntax dazu anregt, sie nur zu Variablen hinzuzufügen, wenn sie nicht einmal benötigt werden (OO-Furbar wartet darauf). Wer würde daran denken, kill () zu verwenden, wenn er seiner toten Flagge einfach ein "property" -Tag hinzufügen könnte?
Bill K

1
Die Frage ist mit einer C ++ - Frage markiert. C ++ unterstützt keine Eigenschaften. Warum sollten Sie dies überhaupt posten?
Thomas Eding

1
@ThomasEding: C ++ ist in Bezug auf überschreibbare Zuweisungsoperatoren einzigartig. Während C ++ den Begriff "Eigenschaft" nicht wie Sprachen wie C # oder VB.NET verwendet, ist das Konzept immer noch vorhanden. In C ++ ist es möglich, die Überladung von Operatoren zu verwenden, sodass foo.bar = biz.baz+5eine Funktion bizzum Auswerten aufgerufen baz, dann fünf hinzugefügt und eine Funktion foozum Festlegen bardes resultierenden Werts aufgerufen wird. Solche Überladungen werden nicht als "Eigenschaften" bezeichnet, können jedoch denselben Zweck erfüllen.
Supercat

25

Dies ist nicht die populärste Meinung, aber ich sehe keinen großen Unterschied.

Setter und Getter sind eine ziemlich schlechte Idee. Ich habe darüber nachgedacht und ehrlich gesagt kann ich in der Praxis keinen Unterschied zwischen einem Setter / Getter einer Eigenschaft und einer öffentlichen Variablen finden.

In THEORY fügen ein Setter und Getter oder eine Eigenschaft einen Ort hinzu, an dem zusätzliche Aktionen ausgeführt werden können, wenn eine Variable gesetzt / geholt wird. Theoretisch isolieren sie Ihren Code von Änderungen.

In Wirklichkeit sehe ich selten Setter und Getter, die zum Hinzufügen einer Aktion verwendet werden, und wenn Sie eine Aktion hinzufügen möchten, möchten Sie sie ALLEN Settern oder Gettern einer Klasse (wie dem Protokollieren) hinzufügen, die Sie zu der Annahme veranlassen sollten, dass dies erforderlich ist sei eine bessere Lösung.

Wenn Sie beim Isolieren von Entwurfsentscheidungen ein Int in ein Long ändern, müssen Sie immer noch Ihre Setter ändern und mindestens jede Zeile überprüfen, die von Hand darauf zugreift - da ist nicht viel Isolation.

Veränderliche Klassen sollten ohnehin standardmäßig vermieden werden, daher sollte das Hinzufügen eines Setters das letzte Mittel sein. Dies wird durch das Builder-Muster verringert, bei dem ein Wert festgelegt werden kann, bis sich das Objekt in einem gewünschten Zustand befindet. Dann kann die Klasse unveränderlich werden und Ihre Setter Ausnahmen auslösen.

Was Getter betrifft, kann ich immer noch keinen großen Unterschied zwischen einem Getter und einer öffentlichen Endvariablen feststellen. Das Problem hierbei ist, dass es in beiden Fällen schlecht ist. Sie sollten nicht nach einem Wert von einem Objekt fragen und daran arbeiten - Sie sollten ein Objekt bitten, eine Operation für Sie durchzuführen.

Übrigens befürworte ich keine öffentlichen Variablen - ich sage, Setter und Getter (und sogar Eigenschaften) sind viel zu nahe dran, bereits öffentliche Variablen zu sein.

Das große Problem ist einfach, dass Leute, die keine OO-Programmierer sind, zu versucht sind, Setter und Getter zu verwenden, um Objekte in Eigenschaftskugeln (Strukturen) zu verwandeln, die herumgereicht und bearbeitet werden, so ziemlich das Gegenteil von der Funktionsweise von objektorientiertem Code.


Eine gute Sache, abgesehen von anderen Antworten, über Getter Setter ist: Ich kann einige Validierungen / Konvertierungen hinzufügen, bevor die eigentliche private Variable ihren Wert annimmt.
WinW

1
@Kiran true, aber wenn Sie es im Konstruktor setzen, können Sie Validierungen haben UND Sie haben eine unveränderliche Klasse. Für Setter sage ich nicht, dass eine öffentlich beschreibbare Variable gut ist, ich sage, dass sogar Setter schlecht sind. Für Getter könnte ich eine öffentliche endgültige Variable als eine gangbare Alternative betrachten.
Bill K

In einigen Sprachen wie C # sind Eigenschaften nicht mit öffentlichen Variablen binärkompatibel. Wenn Sie also eine öffentliche Variable zu einem Teil Ihrer API machen, dann aber eine Validierung hinzufügen möchten, brechen Sie das Programm Ihres Clients, wenn Sie es in konvertieren Ein Besitz. Es ist besser, es von Anfang an zu einem öffentlichen Eigentum zu machen.
Robert Harvey

@RobertHarvey Das bedeutet immer noch, dass Sie Eigenschaften verfügbar machen. Mein gesamter Punkt war, dass das Freilegen von Eigenschaften vermieden werden sollte, und wenn nötig sollten Sie versuchen, sie auf Getter zu beschränken und Ihre Klasse unveränderlich zu machen. Wenn Ihre Klasse unveränderlich ist, warum benötigen Sie dann Code in einem Getter? Daraus folgt, dass Sie möglicherweise auch keinen Getter haben. Wenn Sie nicht in der Lage sind, eine Klasse ohne offen gelegte veränderbare Eigenschaften zu schreiben, ist ein Setter immer besser als eine beschreibbare öffentliche Variable (und ein Schuss in den Fuß ist immer besser als ein Stich in den Darm).
Bill K

1
Ein veränderbarer Zustand ist akzeptabel, aber Sie sollten Ihr Objekt erneut auffordern, etwas für Sie zu tun, nicht etwas für Ihr Objekt zu tun. Wenn Sie also Ihren Zustand ändern, ist ein Setter keine gute Möglichkeit, dies zu tun. Versuchen Sie, eine Geschäftsmethode mit Logik zu schreiben stattdessen darin. Zum Beispiel "move (Up5) statt setZLocation (getZLocation () + 5)".
Bill K

8

Der Benutzer von Gettern und Setzern befasst sich mit dem Prinzip der Kapselung . Auf diese Weise können Sie die Funktionsweise innerhalb der Klasse ändern und alles am Laufen halten.

Wenn zum Beispiel 3 andere Objekte aufrufen foo.bar, um den Wert von bar zu erhalten, und Sie beschließen, den Namen von bar so weit zu ändern, dass Sie ein Problem haben. Wenn die Objekte aufgerufen foo.barwürden, müssten Sie alle Klassen ändern, die dies haben. Wenn ein Setter / Getter verwendet wird, müssen Sie nichts ändern. Eine andere Möglichkeit besteht darin, den Typ der Variablen zu ändern. In diesem Fall fügen Sie dem Getter / Setter einfach etwas Transformationscode hinzu, und schon geht es Ihnen gut.


1
+1 - Die Mitgliedsvariable ist die Implementierung, der Getter und der Setter sind die Schnittstelle. Nur die Schnittstelle sollte öffentlich sein. Außerdem sollten die Get- und Setter-Namen im Hinblick auf eine Abstraktion sinnvoll sein, unabhängig davon, ob ihnen eine einfache Member-Variable oder eine andere Implementierung zugrunde liegt. Es gibt Ausnahmen - Fälle, in denen ein aus eng gekoppelten Klassen aufgebautes Subsystem der einfachste Weg ist -, bei denen jedoch die Datenversteckgrenze ohnehin um eine Ebene nach oben verschoben wird, bis zu der Klasse, die das gesamte Subsystem darstellt.
Steve314

Könnte ich den Getter und / oder Setter nicht einfach vorstellen, wenn ich die Arbeit innerhalb der Klasse ändern muss? Es ist etwas, das in Delphi leicht gemacht werden kann, aber vielleicht nicht in anderen Sprachen?
Marjan Venema

@ marjan Sicher, Sie können den Getter / Setter später jederzeit einführen. Das Problem ist dann, dass Sie zurückgehen und ALLEN Code ändern müssen, der das öffentliche Feld anstelle von Getter / Setter verwendete. Ich bin mir nicht sicher, ob ich derjenige bin, der verwirrt ist oder du bist. 0.o
Pete

3
Persönlich halte ich das für ein falsches Argument. Wenn Sie die Bedeutung einer Variablen ändern, die Variable jedoch einen Getter und Setter hat, spielt es keine Rolle, wie sie heißt. Es ist kein sichtbarer Teil der Benutzeroberfläche. Es ist viel wahrscheinlicher, dass Sie den Namen der Getter und Setter selbst ändern möchten, um den Clients die Änderung oder Klarstellung der Bedeutung der gekapselten Variablen anzuzeigen. In letzterem Fall sind Sie in keiner besseren Position als mit einer öffentlichen Variablen. Dies steht im Widerspruch zu dem Argument, dass eine Variable mit Gettern und Setzern eher exponiert als gekapselt ist.
CB Bailey

1
Eine Refactor-Operation ist in beiden Fällen praktisch kostenlos, daher kaufe ich diese nicht wirklich. Außerdem ist es eine sehr schlechte Form, einen Setter oder Getter zu verwenden, es sei denn, Sie benötigen ihn unbedingt - irgendwann werden Leute, die das Konzept nicht verstehen, einfach automatisch Setter und Getter für Mitglieder hinzufügen, anstatt wenn sie gebraucht werden, was nur Samen tropft des Bösen in Ihrer gesamten Codebasis.
Bill K

5

Mit Gettern und Setzern können Sie auch steuern, welche Inhalte in einer bestimmten Variablen gespeichert werden. Wenn der Inhalt einen bestimmten Typ oder Wert haben muss, kann ein Teil Ihres Setter-Codes sicherstellen, dass der neue Wert diese Anforderungen erfüllt. Wenn die Variable öffentlich ist, können Sie nicht sicherstellen, dass diese Anforderungen erfüllt sind.

Dieser Ansatz macht Ihren Code außerdem anpassungsfähiger und verwaltbarer. Es ist viel einfacher, Änderungen an der Architektur einer Klasse vorzunehmen, wenn Funktionen vorhanden sind, die diese Architektur vor allen anderen Klassen oder Funktionen, die diese Klasse verwenden, verbergen. Die bereits erwähnte Änderung eines Variablennamens ist nur eine von vielen Änderungen, die viel einfacher durchzuführen sind, wenn Sie Funktionen wie Getter und Setter haben. Die allgemeine Idee ist, so viel wie möglich privat zu halten, insbesondere Ihre Klassenvariablen.


Vielen Dank! Ich dachte an denselben Fall, den Sie erwähnt haben. Wenn eine Variable in [0,1] enthalten sein soll, sollte der eingehende Wert in ihrem Setter überprüft werden :)
Oni

Wenn Sie der einzige Entwickler sind, der an einem Projekt arbeitet, ist es leicht zu glauben, dass solche Dinge nutzlos sind. Wenn Sie jedoch mit einem Team zusammenarbeiten, werden Sie feststellen, dass die Gewohnheit, solche Techniken anzuwenden, sehr nützlich ist und Ihnen hilft Vermeiden Sie viele Fehler auf der Straße.
Kenneth

Wenn Sie C # und Eigenschaften verwenden, können Sie mit einer Eigenschaft eine private Instanz festlegen, falls dies aufgrund einer Logik im Setter-Teil der Eigenschaft erforderlich ist.
Developerdoug

2

Sie sagen: "Ich denke, Sie müssen Getter und Setter nur dann verwenden, wenn Sie eine andere Operation als das Set oder das Get ausführen müssen."

Sie sollten Getter und Setter verwenden, wenn Sie zu einem späteren Zeitpunkt möglicherweise eine andere Operation als set and get ausführen müssen und in diesem Fall nicht Tausende von Quellcodezeilen ändern möchten.

Sie sollten Getter und Setter verwenden, wenn Sie nicht möchten, dass jemand die Adresse der Variablen nimmt und weitergibt, was katastrophale Folgen hat, wenn diese Variable dann geändert werden kann, ohne dass der Quellcode dies erwähnt, oder auch nachdem das Objekt nicht mehr vorhanden ist .


In Ihrem letzten Absatz geht es um einen ausgezeichneten Punkt, der in beide Richtungen geht: Durch die Verwendung von Gettern und Setzern wird verhindert, dass anderer Code die Adresse von etwas übernimmt. In Fällen, in denen es bedeutende Vorteile und einige Nachteile hätte, wenn externer Code die Adressen von Dingen übernimmt, könnte eine solche Einschränkung eine schlechte Sache sein. Wenn das Zulassen eines solchen Verhaltens nur wenige Vorteile und schwerwiegende Nachteile hätte, könnte es eine gute Sache sein, es einzuschränken.
Supercat

1

Lassen Sie uns zunächst das Paradigma klarstellen.

  • Datenstrukturen -> Ein Layout des Speichers, das von sachkundigen Funktionen durchlaufen und bearbeitet werden kann.
  • Objekte -> Ein eigenständiges Modul, das seine Implementierung verbirgt und eine Schnittstelle bereitstellt, über die kommuniziert werden kann.

Wo ist ein Getter / Setter nützlich?

Sind Getter / Setter in Datenstrukturen nützlich? Nein.

Eine Datenstruktur ist eine Speicherlayoutspezifikation, die einer Reihe von Funktionen gemeinsam ist und von diesen manipuliert wird.

Im Allgemeinen kann jede alte neue Funktion eine Datenstruktur manipulieren, wenn dies so geschieht, dass die anderen Funktionen es noch verstehen können, dann wird die Funktion der Familie hinzugefügt. Ansonsten ist es eine Schurkenfunktion und eine Fehlerquelle.

Verstehen Sie mich nicht falsch, es könnte mehrere Funktionsfamilien geben, die überall über diese Datenstruktur mit Snitches, Turncoats und Double-Agents streiten. Es ist in Ordnung, wenn jeder seine eigene Datenstruktur hat, mit der er spielen kann, aber wenn er sie teilt ... Stellen Sie sich vor, mehrere kriminelle Familien sind sich nicht einig über Politik, es kann sehr schnell zu einem Durcheinander kommen.

Gibt es eine Möglichkeit, die Datenstruktur zu verschlüsseln, damit Schurkenfunktionen nicht alles durcheinander bringen, wenn die erweiterten Funktionsfamilien dies erreichen können? Ja, sie heißen Objekte.

Sind Getter / Setter in Objekten nützlich? Nein.

Der springende Punkt beim Einbinden einer Datenstruktur in ein Objekt war, sicherzustellen, dass keine unerwünschten Funktionen vorhanden sind. Wenn die Funktion der Familie beitreten wollte, musste sie zuerst gründlich überprüft werden und dann Teil des Objekts werden.

Der Zweck eines Get- und Setters besteht darin, Funktionen außerhalb des Objekts die Möglichkeit zu geben, das Speicherlayout des Objekts direkt zu ändern. Das klingt nach einer offenen Tür, die es Schurken erlaubt ...

Der Edge-Fall

Es gibt zwei Situationen, in denen ein öffentlicher Getter / Setter Sinn macht.

  • Ein Teil der Datenstruktur innerhalb des Objekts wird vom Objekt verwaltet, aber nicht vom Objekt gesteuert.
  • Eine Schnittstelle, die eine allgemeine Abstraktion einer Datenstruktur beschreibt, bei der erwartet wird, dass einige Elemente nicht die Kontrolle über das implementierende Objekt haben.

Container und Containerschnittstellen sind perfekte Beispiele für diese beiden Situationen. Der Container verwaltet die Datenstrukturen (verknüpfte Liste, Karte, Baum) intern, übergibt jedoch die Kontrolle über das spezifische Element an alle und verschiedene. Die Schnittstelle abstrahiert dies und ignoriert die Implementierung vollständig und beschreibt nur die Erwartungen.

Leider machen viele Implementierungen das falsch und definieren die Schnittstelle dieser Art von Objekten, um einen direkten Zugriff auf das eigentliche Objekt zu ermöglichen. So etwas wie:

interface Container<T>
{
    typedef ...T... TRef; //<somehow make TRef to be a reference or pointer to the memory location of T
    TRef item(int index); 
}

Das ist kaputt. Die Implementierungen von Container müssen die Kontrolle über ihre Interna explizit an denjenigen übergeben, der sie verwendet. Ich habe noch keine Sprache mit veränderlichem Wert gesehen, bei der dies in Ordnung ist (Sprachen mit Semantik mit unveränderlichem Wert sind per Definition in Bezug auf Datenkorruption in Ordnung, jedoch nicht unbedingt in Bezug auf Datenspionage).

Sie können den Get / Setter verbessern / korrigieren, indem Sie nur die Kopiersemantik oder einen Proxy verwenden:

interface Proxy<T>
{
     operator T(); //<returns a copy
     ... operator ->(); //<permits a function call to be forwarded to an element
     Proxy<T> operator=(T); //< permits the specific element to be replaced/assigned by another T.
}

interface Container<T>
{
     Proxy<T> item(int index);
     T item(int index); //<When T is a copy of the original value.
     void item(int index, T new_value); //<where new_value is used to replace the old value
}

Möglicherweise könnte eine Rogue-Funktion hier noch Chaos anrichten (mit genügend Aufwand sind die meisten Dinge möglich), aber die Kopiersemantik und / oder der Proxy verringern die Wahrscheinlichkeit für eine Reihe von Fehlern.

  • Überlauf
  • Unterlauf
  • Interaktionen mit dem Unterelement sind typüberprüft / typüberprüfbar (in Typ-Lose-Sprachen ist dies ein Segen)
  • Das tatsächliche Element kann speicherresident sein oder nicht.

Private Getter / Setter

Dies ist die letzte Bastion von Gettern und Setzern, die direkt an dem Typ arbeiten. Tatsächlich würde ich diese Getter und Setter nicht einmal als Accessoren und Manipulatoren bezeichnen.

In diesem Zusammenhang erfordert die Manipulation eines bestimmten Teils der Datenstruktur manchmal immer / fast immer / im Allgemeinen eine bestimmte Buchführung. Sagen wir, wenn Sie das Stammverzeichnis eines Baums aktualisieren, muss der Look-Aside-Cache gelöscht werden, oder wenn Sie auf das externe Datenelement zugreifen, muss eine Sperre erlangt / aufgehoben werden. In diesen Fällen ist es sinnvoll, den DRY-Principal anzuwenden und diese Aktionen zusammenzufassen.

Im privaten Kontext ist es für die anderen Funktionen in der Familie immer noch möglich, diese "Get-and-Setter" zu umgehen und die Datenstruktur zu manipulieren. Deshalb betrachte ich sie eher als Accessoren und Manipulatoren. Sie können direkt auf die Daten zugreifen oder sich auf ein anderes Familienmitglied verlassen, um diesen Teil richtig zu machen.

Geschützte Getter / Setter

In einem geschützten Kontext unterscheidet es sich nicht sonderlich von einem öffentlichen Kontext. Fremd möglicherweise Rogue-Funktionen wollen Zugriff auf die Datenstruktur. Also nein, wenn sie existieren, funktionieren sie wie öffentliche Getter / Setter.


0

Aus C ++ - Erfahrung ist der Setter / Getter für zwei Szenarien hilfreich:

  1. Wenn Sie vor dem Zuweisen von Werten wie Zeichenfolgen oder Arrays eine Überprüfung der Integrität durchführen müssen.
  2. Dies kann hilfreich sein, wenn eine Überladung von Funktionen erforderlich ist, z. B. die Zuweisung von int zu bool, wenn -1 (oder negative Werte) falsch ist. und umgekehrt für Getter.

Abgesehen davon ist Sicherheit ein wichtiger Punkt, aber seine Bedeutung ist auf sehr wenige Entwicklungsanwendungen wie Login- oder Datenbankzugriffsmodule beschränkt. Warum sich die Mühe machen, extra zu programmieren, wenn nur wenige Leute Ihr Modul benutzen?

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.