Öffentliche Daten Mitglieder vs Getters, Setter


78

Ich arbeite derzeit in Qt und damit in C ++. Ich habe Klassen mit privaten Datenmitgliedern und öffentlichen Mitgliederfunktionen. Ich habe öffentliche Getter und Setter für die Datenelemente, die in der Klasse verfügbar sind.

Meine Frage ist nun, ob wir Getter und Setter für Datenelemente in unseren Klassen haben. Was bringt es dann, diese Datenelemente als privat zu kennzeichnen? Ich bin damit einverstanden, dass private Datenmitglieder in Basisklassen logisch klingen. Abgesehen davon scheint es für mich nicht logisch zu sein, private Mitglieder zu haben, ebenso wie ihre Getter und Setter.

Oder können wir stattdessen alle Variablen als öffentlich machen, sodass überhaupt keine Getter und Setter benötigt werden? Ist es eine gute Praxis, diese zu haben? Ich weiß, dass private Mitglieder die Datenabstraktion sicherstellen, aber Getter und Setter ermöglichen den Zugriff auf diese Variablen recht einfach. Hinweise hierzu sind willkommen.


1
Um die Änderung einer Variablen einer Klasse in einer anderen Klasse zu steuern, können wir steuern, indem wir die Variable privat machen, wie der Wert geändert wird. Wenn beispielsweise in einer anderen Klasse die Variable mit einem Setter auf negativ geändert wird, können wir eine Ausnahme auslösen, um dies nicht zu tun auf negativ gesetzt. Dabei muss die Variable in privat umgewandelt werden, und um auf diese private Variable zuzugreifen, können wir getter verwenden.
Karthik Gorijavolu

Antworten:


78

Weder. Sie sollten Methoden haben, die Dinge tun. Wenn eines dieser Dinge zufällig einer bestimmten internen Variablen entspricht, ist das großartig, aber es sollte nichts geben, das dies den Benutzern Ihrer Klasse telegraphiert.

Private Daten sind privat, sodass Sie die Implementierung jederzeit ersetzen können (und vollständige Neuerstellungen durchführen können, dies ist jedoch ein anderes Problem). Sobald Sie den Genie aus der Flasche gelassen haben, können Sie ihn nicht mehr hineinschieben.

EDIT: Nach einem Kommentar habe ich auf eine andere Antwort gemacht.

Mein Punkt hier ist, dass Sie die falsche Frage stellen. Es gibt keine Best Practice in Bezug auf die Verwendung von Gettern / Setzern oder die Verwendung öffentlicher Mitglieder. Es gibt nur das, was für Ihr spezifisches Objekt am besten ist und wie es eine bestimmte reale Welt modelliert (oder eine imaginäre Sache, vielleicht im Fall eines Spiels).

Persönlich sind Getter / Setter das kleinere von zwei Übeln. Denn sobald Sie anfangen, Getter / Setter zu erstellen, hören die Leute auf, Objekte mit einem kritischen Blick darauf zu entwerfen, welche Daten sichtbar sein sollten und welche nicht. Bei öffentlichen Mitgliedern ist es noch schlimmer, weil die Tendenz besteht, alles öffentlich zu machen.

Untersuchen Sie stattdessen, was das Objekt tut und was es bedeutet, dass etwas dieses Objekt ist. Erstellen Sie dann Methoden, die eine natürliche Schnittstelle zu diesem Objekt bieten. Diese natürliche Schnittstelle beinhaltet das Freilegen einiger interner Eigenschaften mithilfe von Gettern und Setzern. Aber der wichtige Teil ist, dass Sie im Voraus darüber nachgedacht und die Getter / Setter aus einem designbegründeten Grund erstellt haben.


4
Ein weiterer nützlicher Unterschied: Sie können leicht einen Debugging-Haltepunkt für eine Mitgliedsfunktion platzieren, aber es ist viel schwieriger, einen Debugging-Haltepunkt für alle Instanziierungen einer Klasse an einem Speicherort zu platzieren. Dies führt zu Fragen wie: Muss ich wissen, wann dies geändert wird oder auf was zugegriffen wird? Gibt es Beziehungen zwischen diesen Datenelementen, die gepflegt werden müssen? Was sind die Multithreading-Sicherheitsanforderungen?
Jason Harrison

37

Nein, es ist nicht einmal im entferntesten dasselbe.

Es gibt verschiedene Schutz- / Implementierungsstufen, die durch unterschiedliche Ansätze für eine Klassenschnittstelle erreicht werden können:


1. Mitglied der öffentlichen Daten:

  • Bietet sowohl Lese- als auch Schreibzugriff (wenn nicht const) auf das Datenelement
  • macht die Tatsache verfügbar, dass das Datenobjekt physisch vorhanden ist und physisch ein Mitglied dieser Klasse ist (ermöglicht das Erstellen von Zeigern vom Typ Zeiger auf Element für dieses Datenelement)
  • Bietet einen Wertzugriff auf das Datenelement (ermöglicht das Erstellen gewöhnlicher Zeiger auf das Element).


2. Eine Methode, die einen Verweis auf ein Datenelement (möglicherweise auf ein privates Datenelement) zurückgibt:

  • Bietet sowohl Lese- als auch Schreibzugriff (wenn nicht const) auf die Daten
  • legt die Tatsache offen, dass das Datenobjekt physisch vorhanden ist, stellt jedoch nicht dar, dass es physisch ein Mitglied dieser Klasse ist (erlaubt es nicht, Zeiger vom Typ Zeiger auf Mitglied auf die Daten zu erstellen)
  • Bietet einen wertvollen Zugriff auf die Daten (ermöglicht das Erstellen gewöhnlicher Zeiger darauf).


3. Getter- und / oder Setter-Methoden (möglicherweise Zugriff auf ein privates Datenelement):

  • Bietet sowohl Lese- als auch Schreibzugriff auf die Eigenschaft
  • legt nicht die Tatsache offen , dass ein Datenobjekt physisch vorhanden ist, geschweige denn physisch in dieser Klasse vorhanden ist (erlaubt es nicht, Zeiger vom Typ Zeiger auf Mitglied auf diese Daten oder irgendeine Art von Zeigern für diese Angelegenheit zu erstellen).
  • bietet keinen Wertzugriff auf die Daten (erlaubt es nicht, gewöhnliche Zeiger darauf zu erstellen)

Der Getter / Setter-Ansatz enthüllt nicht einmal die Tatsache, dass die Eigenschaft von einem physischen Objekt implementiert wird. Das heißt, hinter dem Getter / Setter-Paar befindet sich möglicherweise kein physisches Datenelement.

Wenn man das oben genannte berücksichtigt, ist es seltsam zu sehen, dass jemand behauptet, ein Getter-Setter-Paar sei dasselbe wie ein öffentliches Datenmitglied. Tatsächlich haben sie nichts gemeinsam.

Natürlich gibt es Variationen von jedem Ansatz. Eine Getter-Methode könnte beispielsweise eine konstante Referenz auf die Daten zurückgeben, die sie irgendwo zwischen (2) und (3) platzieren würde.


1
@ AndreyT, wirklich gut ... Aber ist es eine gute Praxis, Getter und Setter für private Mitglieder zu haben? Zumindest für diejenigen Mitglieder, die möglicherweise so oft abgerufen und geändert werden ...
LiaK

3
Sie sind möglicherweise nicht identisch, werden jedoch für denselben Zweck verwendet - um den öffentlichen Zugriff auf eine Eigenschaft eines Objekts zu ermöglichen.
Mark Ransom

3
Sie betrachten dieses Formular aus der Sicht der C ++ - Sprache - meine Antwort ist aus der Perspektive des Anwendungsdesigns.

1
+1 für den Hinweis, dass ein Getter keine Referenz oder keinen Zeiger auf die Daten zurückgeben muss. In der Lage zu sein, eine Kopie zu erhalten, ist viel anders als in der Lage zu sein, den Wert zu erhalten.
Tloach

1
@liaK - Es hat nichts mit der guten Praxis zu tun und alles mit dem, was Ihr Objektmodell tut. Siehe meine Antwort oben. Sie stellen die falsche Frage.
jmucchiello

24

Wenn Sie für jedes Ihrer Datenelemente Getter und Setter haben, macht es keinen Sinn, die Daten privat zu machen. Aus diesem Grund ist es eine schlechte Idee, Getter und Setter für jedes Ihrer Datenelemente zu haben. Betrachten Sie die Klasse std :: string - sie hat (wahrscheinlich) EINEN Getter, die Funktion size () und überhaupt keine Setter.

Oder betrachten Sie ein BankAccountObjekt - sollten wir SetBalance()Setter haben, um das aktuelle Gleichgewicht zu ändern? Nein, die meisten Banken werden Ihnen nicht dafür danken, dass Sie so etwas umgesetzt haben. Stattdessen wollen wir so etwas wie ApplyTransaction( Transaction & tx ).


1
Sie wollen damit sagen, dass wir vermeiden sollten, Getter und Setter für die Datenmitglieder zu haben?
LiaK

2
+1 Sie möchten eine Schnittstelle nach "Task" / "Operation" haben, keine Getter / Setter oder öffentliche Daten.
Mark B

3
Neil, ich denke das ist kein guter Vorschlag. Getter und Setter sind für das Eigenschaftssystem von Qt von entscheidender Bedeutung. Sie können keine Eigenschaften ohne sie haben. Getter und Setter können auch andere notwendige Dinge tun, wie einen Mutex sperren, eine Datenbank abfragen, implizite Freigabe, Referenzzählung, verzögertes Laden und so weiter.
CMircea

2
Dein erster Satz hat mich irgendwie umgehauen. Es ist immer noch sinnvoll, die Daten privat zu machen, selbst wenn Sie Getter und Setter dafür haben. Das scheint orthogonal zu Ihrem größeren Punkt zu sein, dass es in Ordnung (sogar wünschenswert) ist, private Daten ohne Getter und Setter zu haben.
Bill the Lizard

2
@ Bill Sie haben Recht - Übertreibung für die Wirkung dort. Mein Punkt ist die allgemeine Schlechtigkeit von get / set-Funktionen.

11

Mit Getters und Setter können Sie Logik auf die Eingabe / Ausgabe der privaten Mitglieder anwenden und so den Zugriff auf die Daten steuern (Kapselung für diejenigen, die ihre OO-Begriffe kennen).

Öffentliche Variablen lassen die Daten Ihrer Klasse für unkontrollierte und nicht validierte Manipulationen öffentlich zugänglich, was fast immer unerwünscht ist.

Über diese Dinge muss man auch langfristig nachdenken. Möglicherweise haben Sie jetzt keine Validierung (weshalb öffentliche Variablen eine gute Idee zu sein scheinen), aber es besteht die Möglichkeit, dass sie später hinzugefügt werden. Wenn Sie sie vorzeitig hinzufügen, bleibt das Framework verlassen, sodass weniger Änderungen vorgenommen werden müssen, ganz zu schweigen davon, dass die Validierung den abhängigen Code auf diese Weise nicht beschädigt.

Beachten Sie jedoch, dass dies nicht bedeutet, dass jede private Variable einen eigenen Getter / Setter benötigt. Neil bringt in seinem Bankbeispiel einen guten Punkt vor, dass Getters / Setter manchmal einfach keinen Sinn ergeben.


11

Machen Sie die Daten öffentlich. Für den (eher unwahrscheinlichen) Fall, dass Sie eines Tages Logik im "Getter" oder "Setter" benötigen, können Sie den Datentyp in eine Proxy-Klasse ändern, die überlastet operator=und / oder operator T(wobei T = welcher Typ Sie jetzt verwenden). die notwendige Logik zu implementieren.

Bearbeiten: Die Idee, dass das Steuern des Zugriffs auf die Daten eine Kapselung darstellt, ist grundsätzlich falsch. Bei der Kapselung geht es darum, die Details der Implementierung (im Allgemeinen!) Zu verbergen und nicht den Zugriff auf Daten zu kontrollieren.

Die Kapselung ist eine Ergänzung zur Abstraktion: Die Abstraktion befasst sich mit dem von außen sichtbaren Verhalten des Objekts, während die Kapselung das Ausblenden der Details der Implementierung dieses Verhaltens behandelt.

Die Verwendung eines Getters oder Setters reduziert tatsächlich den Abstraktionsgrad und macht die Implementierung verfügbar. Der Clientcode muss sich darüber im Klaren sein, dass diese bestimmte Klasse logisch "Daten" als Funktionspaar (Getter und Setter) implementiert. Die Verwendung eines Proxys, wie ich oben vorgeschlagen habe, bietet eine echte Kapselung - mit Ausnahme eines obskuren Eckfalls wird die Tatsache vollständig ausgeblendet , dass das, was logischerweise ein Datenelement ist, tatsächlich über ein Funktionspaar implementiert wird.

Dies muss natürlich im Kontext gehalten werden: Für einige Klassen sind "Daten" überhaupt keine gute Abstraktion. Im Allgemeinen ist dies vorzuziehen , wenn Sie Operationen auf höherer Ebene anstelle von Daten bereitstellen können . Dennoch gibt es Klassen, für die die am besten verwendbare Abstraktion das Lesen und Schreiben von Daten ist - und wenn dies der Fall ist, sollten die (abstrahierten) Daten wie alle anderen Daten sichtbar gemacht werden. Die Tatsache, dass das Abrufen oder Festlegen des Werts mehr als nur das einfache Kopieren von Bits beinhalten kann, ist ein Implementierungsdetail, das dem Benutzer verborgen bleiben sollte.


2
"Sie können den Datentyp in eine Proxy-Klasse ändern" - ja, das können Sie, aber es ist ein Pflaster, das wahrscheinlich mehr Probleme bereitet, als es wert ist.
Mark Ransom

3
Ob Getter / Setter ein gutes Design sind oder nicht, "es gibt eine hackige Problemumgehung" ist kein Argument, sondern eine Ausrede.
BlueRaja - Danny Pflughoeft

3
@ BlueRaja: Unsinn. Siehe die Bearbeitung. Tatsache ist, dass entweder die Daten vollständig unsichtbar sein sollten (nicht öffentlich und auch kein Getter oder Setter) oder als Daten sichtbar sein sollten. In diesem Fall legen Getter und Setter die falsche Abstraktion offen.
Jerry Coffin

3
@Rob: Einfache Statistiken - selbst in Code, der seit Jahrzehnten gepflegt wird , bleibt die überwiegende Mehrheit der Getter / Setter nichts als einfache Durchgänge, die überhaupt nichts Nützliches bewirken.
Jerry Coffin

4
@ Jerry ist richtig. Machen Sie die Daten entweder öffentlich oder machen Sie die Klasse abstrakt und verschieben Sie die Daten in eine Unterklasse. Der Mittelweg - private Daten mit öffentlichen Gettern und Setzern - ist der Hack.
John Dibling

5

Wenn Sie sich ziemlich sicher sind, dass Ihre Logik einfach ist und Sie beim Lesen / Schreiben einer Variablen nie etwas anderes tun müssen, ist es besser, die Daten öffentlich zu halten. Im C ++ - Fall bevorzuge ich die Verwendung von struct anstelle von class, um die Tatsache hervorzuheben, dass die Daten öffentlich sind.

Oft müssen Sie jedoch beim Zugriff auf Datenelemente einige andere Dinge tun, oder Sie möchten sich die Freiheit geben, diese Logik später hinzuzufügen. In diesem Fall sind Getter und Setter eine gute Idee. Ihre Änderung ist für die Kunden Ihres Codes transparent.

Ein einfaches Beispiel für zusätzliche Funktionen: Möglicherweise möchten Sie bei jedem Zugriff auf eine Variable eine Debug-Zeichenfolge protokollieren.


5

Abgesehen von den Kapselungsproblemen (die Grund genug sind) ist es sehr einfach, einen Haltepunkt festzulegen, wenn auf die Variable gesetzt wird / zugegriffen wird, wenn Sie Getter / Setter haben.


4

Gründe für die Verwendung öffentlicher Felder anstelle von Gettern und Setzern sind:

  1. Es gibt keine illegalen Werte.
  2. Es wird erwartet, dass der Client es bearbeitet.
  3. Um Dinge wie object.XY = Z schreiben zu können.
  4. Ein starkes Versprechen zu geben, dass der Wert nur ein Wert ist und keine Nebenwirkungen damit verbunden sind (und dies auch in Zukunft nicht sein wird).

Abhängig davon, an welcher Art von Software Sie arbeiten, können dies alles wirklich Ausnahmefälle sein (und wenn Sie glauben, auf eine gestoßen zu sein, liegen Sie wahrscheinlich falsch), oder sie können die ganze Zeit auftreten. Es kommt wirklich darauf an.

(Aus zehn Fragen zur wertorientierten Programmierung .)


3

Aus rein praktischen Gründen würde ich vorschlagen, dass Sie zunächst alle Ihre Datenmitglieder privat machen UND ihre Getter und Setter privat machen. Wenn Sie herausfinden, was der Rest der Welt (dh Ihre "(l) Benutzergemeinschaft") tatsächlich benötigt, können Sie die entsprechenden Getter und / oder Setter verfügbar machen oder entsprechend kontrollierte öffentliche Zugriffsmethoden schreiben.

Außerdem (zu Neils Gunsten) ist es manchmal nützlich, während der Debugging-Zeit einen geeigneten Ort zum Aufhängen von Debug-Ausdrucken und anderen Aktionen zu haben, wenn ein bestimmtes Datenelement gelesen oder geschrieben wird. Mit Gettern und Setzern ist dies einfach. Bei Mitgliedern öffentlicher Daten ist es ein großer Schmerz im hinteren Bereich.


Ich finde private Get / Set-Funktionen während der Entwicklung sehr nützlich, selbst wenn sie kein Geschäft in der öffentlichen Oberfläche haben.
Dennis Zickefoose

2

Ich habe immer gedacht, dass Getter und Setter in den meisten Programmiersprachen absichtlich ausführlich sind, damit Sie zweimal darüber nachdenken, sie zu verwenden. Warum muss Ihr Anrufer über das Innenleben Ihrer Klasse Bescheid wissen, sollte die Frage im Kopf sein .


2

Ich glaube, dass es nutzlos ist, Getter und Setter nur zum Abrufen und Einstellen des Werts zu verwenden. Bei solchen Methoden gibt es keinen Unterschied zwischen einem öffentlichen und einem privaten Mitglied. Verwenden Sie Getter und Setter nur, wenn Sie die Werte irgendwie steuern müssen oder wenn Sie der Meinung sind, dass dies in Zukunft nützlich sein könnte (wenn Sie eine Logik hinzufügen, können Sie den Rest des Codes nicht bearbeiten).

Lesen Sie als Referenz die C ++ - Richtlinien (C.131).


1

Ich schlage vor, dass Sie keine öffentlichen Datenelemente haben (außer POD-Strukturen). Ich empfehle auch nicht, dass Sie Getter und Setter für alle Ihre Datenmitglieder haben. Definieren Sie stattdessen eine saubere öffentliche Schnittstelle für Ihre Klasse. Dies kann Methoden umfassen, die Eigenschaftswerte abrufen und / oder festlegen, und diese Eigenschaften können als Elementvariablen implementiert werden. Aber machen Sie nicht Getter und Setter für alle Ihre Mitglieder.

Die Idee ist, dass Sie Ihre Schnittstelle von Ihrer Implementierung trennen, sodass Sie die Implementierung ändern können, ohne dass die Benutzer der Klasse ihren Code ändern müssen. Wenn Sie alles durch Getter und Setter verfügbar machen, haben Sie nichts gegenüber der Verwendung öffentlicher Daten verbessert.


PODS (Strukturen) stellen eine signifikante Ausnahme von der Regel "Keine öffentlichen Datenmitglieder" dar. Es ist schade, dass Microsoft beim Design einiger .net-Werttypen keine Ausnahme für sie gemacht hat. Lese- / Schreibstruktur-Eigenschaften in .net weisen seltsame, eigenartige Verhaltensweisen auf, die bei exponierten Strukturfeldern nicht der Fall sind.
Supercat

1

Durch die Verwendung von Gettern und Setzern können Sie die Art und Weise ändern, in der Sie dem Benutzer die Werte geben.

Folgendes berücksichtigen:

double premium;
double tax;

Sie schreiben dann überall Code mit diesem premiumWert, um die Prämie zu erhalten:

double myPremium = class.premium;

Ihre Spezifikationen haben sich gerade geändert und Premium muss aus Sicht des Benutzers sein premium + tax

Sie müssen überall ändern, wo dieser premiumWert in Ihrem Code verwendet wird, und ihn ergänzen tax.

Wenn Sie es stattdessen als solches implementiert haben:

double premium;
double tax;

double GetPremium(){return premium;};

Ihr gesamter Code würde verwendet GetPremium()und Ihre taxÄnderung wäre eine Zeile:

double premium;
double tax;

double GetPremium(){return premium + tax;};

1
Und wenn Sie dann die Prämie ohne Steuer brauchen, wie nennen Sie den neuen Getter? Erstellen Sie stattdessen eine Methode, um etwas Reales auf Ihrem Objekt zu berechnen, und lassen Sie es entscheiden, ob es Prämie und Steuer kombiniert.
jmucchiello

Ja, ich nehme an, mein Beispiel ist kein echter Getter / Setter. Ich habe nur versucht zu veranschaulichen, dass Getter und Setter es Ihnen ermöglichen, die Implementierung unabhängig davon zu schreiben, was sich hinter der Haube abspielt. Ich nehme an, ein besseres Beispiel wäre, wenn sich die variable Prämie in m_dPremium oder so ändern würde.
Ben Burnett

0

Der Rückgabewert wirkt sich auch auf die Verwendung von Gettern und Setzern aus. Es ist ein Unterschied, den Wert einer Variablen abzurufen oder Zugriff auf eine private Datenelementvariable zu erhalten. By-Value behält Integrität, By-Reference oder By-Pointer nicht so sehr bei.


0

Getter und Setter existieren hauptsächlich, damit wir steuern können, wie Mitglieder abgerufen und wie sie gesetzt werden. Getter und Setter gibt es nicht nur, um auf ein bestimmtes Mitglied zuzugreifen, sondern um sicherzustellen, dass es möglicherweise bestimmte Bedingungen erfüllt, bevor wir versuchen, ein Mitglied festzulegen, oder wenn wir es abrufen, können wir steuern, dass wir eine Kopie davon zurückgeben Mitglied im Fall eines nicht-primitiven Typs. Insgesamt sollten Sie versuchen, g / s'er zu verwenden, wenn Sie eine Pipeline erstellen möchten, mit der ein Datenelement interagiert werden soll, ohne dass dies dazu führen würde, dass das Mitglied adhoc verwendet wird.

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.