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.
Antworten:
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.
quelle
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:
2. Eine Methode, die einen Verweis auf ein Datenelement (möglicherweise auf ein privates Datenelement) zurückgibt:
3. Getter- und / oder Setter-Methoden (möglicherweise Zugriff auf ein privates Datenelement):
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.
quelle
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
BankAccount
Objekt - sollten wirSetBalance()
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 wieApplyTransaction( Transaction & tx )
.quelle
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.
quelle
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 / oderoperator 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.
quelle
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.
quelle
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.
quelle
Gründe für die Verwendung öffentlicher Felder anstelle von Gettern und Setzern sind:
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 .)
quelle
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.
quelle
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 .
quelle
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).
quelle
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.
quelle
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
premium
Wert, 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
premium
Wert in Ihrem Code verwendet wird, und ihn ergänzentax
.Wenn Sie es stattdessen als solches implementiert haben:
double premium; double tax; double GetPremium(){return premium;};
Ihr gesamter Code würde verwendet
GetPremium()
und Ihretax
Änderung wäre eine Zeile:double premium; double tax; double GetPremium(){return premium + tax;};
quelle
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.
quelle
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.
quelle