In SO wurden einige Fragen zu Accessor-Methoden in C ++ gestellt, aber keine konnte meine Neugier auf das Problem befriedigen.
Ich versuche, Accessoren nach Möglichkeit zu meiden, weil ich, wie Stroustrup und andere berühmte Programmierer, eine Klasse mit vielen von ihnen als Zeichen einer schlechten OO betrachte. In C ++ kann ich in den meisten Fällen einer Klasse mehr Verantwortung hinzufügen oder das Schlüsselwort friend verwenden, um sie zu vermeiden. In einigen Fällen benötigen Sie jedoch wirklich Zugriff auf bestimmte Klassenmitglieder.
Es gibt mehrere Möglichkeiten:
1. Verwenden Sie überhaupt keine Accessoren
Wir können nur die jeweiligen Mitgliedsvariablen veröffentlichen. Dies ist ein No-Go in Java, scheint aber mit der C ++ - Community in Ordnung zu sein. Ich bin jedoch etwas besorgt über Fälle, in denen eine explizite Kopie oder ein schreibgeschützter (const) Verweis auf ein Objekt zurückgegeben werden sollte. Ist das übertrieben?
2. Verwenden Sie get / set-Methoden im Java-Stil
Ich bin mir nicht sicher, ob es überhaupt von Java stammt, aber ich meine das:
int getAmount(); // Returns the amount
void setAmount(int amount); // Sets the amount
3. Verwenden Sie objektive Get / Set-Methoden im C-Stil
Das ist ein bisschen komisch, aber anscheinend immer häufiger:
int amount(); // Returns the amount
void amount(int amount); // Sets the amount
Damit dies funktioniert, müssen Sie einen anderen Namen für Ihre Mitgliedsvariable finden. Einige Leute fügen einen Unterstrich hinzu, andere stellen "m_" voran. Ich mag es auch nicht.
Welchen Stil benutzt du und warum?
Antworten:
Aus meiner Sicht als mit 4 Millionen Zeilen C ++ - Code (und das ist nur ein Projekt) aus Wartungssicht würde ich sagen:
Es ist in Ordnung, keine Getter / Setter zu verwenden, wenn Mitglieder unveränderlich (dh
const
) oder einfach ohne Abhängigkeiten sind (wie eine Punktklasse mit Mitgliedern X und Y).Wenn nur ein Mitglied ist, ist
private
es auch in Ordnung, Getter / Setter zu überspringen. Ich zähle auch Mitglieder interner Zuhälterklassen, alsprivate
ob die .cpp-Einheit kleiner wäre.Wenn ein Mitglied
public
oderprotected
(protected
genauso schlecht wiepublic
) und nichtconst
, nicht einfach oder abhängig ist, verwenden Sie Getter / Setter.Als Wartungsmann ist mein Hauptgrund, warum ich Getter / Setter haben möchte, der, dass ich dann einen Platz habe, an dem ich Haltepunkte / Protokollierung / etwas anderes setzen kann.
Ich bevorzuge den Stil von Alternative 2., da dieser durchsuchbarer ist (eine Schlüsselkomponente beim Schreiben von wartbarem Code).
quelle
2) ist die beste IMO, weil es Ihre Absichten am klarsten macht.
set_amount(10)
ist sinnvoller alsamount(10)
und als netter Nebeneffekt erlaubt ein Mitglied namensamount
.Öffentliche Variablen sind normalerweise eine schlechte Idee, da es keine Kapselung gibt. Angenommen, Sie müssen einen Cache aktualisieren oder ein Fenster aktualisieren, wenn eine Variable aktualisiert wird. Schade, wenn deine Variablen öffentlich sind. Wenn Sie eine festgelegte Methode haben, können Sie diese dort hinzufügen.
quelle
getId()
Aufrufs: Indem Sie diese Logik in einen Getter / Setter einfügen, verletzen Sie den Vertrag der Accessor-Methode. Sie werden die Client-Code-Kompilierung nicht auf diese Weise unterbrechen, aber Sie werden sicherlich die Laufzeitoperation unterbrechen, was noch schlimmer ist. Weil niemand in einem vernünftigen Geisteszustand davon ausgehen wird, dass Ihr Getter beispielsweise manchmal eine Speicherzuweisung vornehmen oder Ihr Setter in ein Dateisystem schreiben könnte. In all diesen Szenarien müssen Sie Redesign für die neuen Anforderungen.Ich benutze diesen Stil nie. Weil es die Zukunft Ihres Klassendesigns einschränken kann und explizite Getter oder Setter mit guten Compilern genauso effizient sind.
In der Realität erzeugen explizite Inline-Getter oder -Setter natürlich ebenso viel zugrunde liegende Abhängigkeit von der Klassenimplementierung. Sie reduzieren nur die semantische Abhängigkeit. Sie müssen immer noch alles neu kompilieren, wenn Sie sie ändern.
Dies ist mein Standardstil, wenn ich Zugriffsmethoden verwende.
Dieser Stil scheint mir zu "klug". Ich benutze es in seltenen Fällen, aber nur in Fällen, in denen ich wirklich möchte, dass sich der Accessor so gut wie möglich wie eine Variable fühlt.
Ich denke, es gibt einen Fall für einfache Taschen mit Variablen mit möglicherweise einem Konstruktor, um sicherzustellen, dass sie alle auf etwas Vernünftiges initialisiert sind. Wenn ich das mache, mache ich es einfach zu einem
struct
und lasse alles öffentlich.quelle
Das ist ein guter Stil, wenn wir nur
pure
Daten darstellen wollen.Ich mag es nicht :) weil
get_/set_
es wirklich unnötig ist, wenn wir sie in C ++ überladen können.STL verwendet diesen Stil wie
std::streamString::str
undstd::ios_base::flags
, außer wenn dies vermieden werden sollte! wann? Wenn der Name der Methode mit dem Namen eines anderen Typs in Konflikt steht, wird derget_/set_
Stil verwendet, z. B.std::string::get_allocator
wegenstd::allocator
.quelle
Im Allgemeinen halte ich es für keine gute Idee, zu viele Getter und Setter von zu vielen Entitäten im System zu verwenden. Dies ist nur ein Hinweis auf ein schlechtes Design oder eine falsche Kapselung.
Wenn jedoch ein solches Design überarbeitet werden muss und der Quellcode verfügbar ist, würde ich lieber das Visitor Design-Muster verwenden. Der Grund ist:
Grundidee ist:
quelle
Ich würde Accessoren nicht von der Verwendung ausschließen. Mai für einige POD-Strukturen, aber ich halte sie für eine gute Sache (einige Accessoren haben möglicherweise auch zusätzliche Logik).
Die Namenskonvention spielt keine Rolle, wenn Sie in Ihrem Code konsistent sind. Wenn Sie mehrere Bibliotheken von Drittanbietern verwenden, verwenden diese möglicherweise ohnehin unterschiedliche Namenskonventionen. Es ist also Geschmackssache.
quelle
Eine zusätzliche Möglichkeit könnte sein:
int& amount();
Ich bin nicht sicher, ob ich es empfehlen würde, aber es hat den Vorteil, dass die ungewöhnliche Notation Benutzer davon abhalten kann, Daten zu ändern.
str.length() = 5; // Ok string is a very bad example :)
Manchmal ist es vielleicht nur die gute Wahl:
image(point) = 255;
Eine andere Möglichkeit ist wiederum die funktionale Notation, um das Objekt zu ändern.
Auf diese Weise kann die Gefahr- / Bearbeitungsfunktion in einem separaten Namespace mit eigener Dokumentation entfernt werden. Dieser scheint natürlich mit generischer Programmierung zu kommen.
quelle
operator =
anstelle eines Verweises auf ein internes Datenelement enthält.operator=
im ersten Fall offensichtlich zumindest eine gute Sache. In einigen Fällen würde ich die zweite verwenden, wenn ich Bearbeitungsfunktionen quer zur Klassenhierarchie bereitstellen möchte und wenn es sinnvoll ist, diese Funktionen klar von der Klasse zu trennen. Es ist wahrscheinlich eine schlechte Idee für einen einfachen Setter.Ich habe die Idealisierung von Klassen anstelle von integralen Typen gesehen, um auf aussagekräftige Daten zu verweisen.
Etwas wie dieses unten nutzt C ++ - Eigenschaften im Allgemeinen nicht gut aus:
struct particle { float mass; float acceleration; float velocity; } p;
Warum? Denn das Ergebnis von p.mass * p.acceleration ist ein Float und keine Kraft wie erwartet.
Die Definition von Klassen einen Zweck zu bezeichnen (auch wenn es ein Wert ist, wie Menge bereits erwähnt) macht mehr Sinn, und es uns ermöglichen , etwas zu tun:
struct amount { int value; amount() : value( 0 ) {} amount( int value0 ) : value( value0 ) {} operator int()& { return value; } operator int()const& { return value; } amount& operator = ( int const newvalue ) { value = newvalue; return *this; } };
Sie können vom Operator int implizit auf den Betragswert zugreifen. Außerdem:
struct wage { amount balance; operator amount()& { return balance; } operator amount()const& { return balance; } wage& operator = ( amount const& newbalance ) { balance = newbalance; return *this; } };
Getter / Setter-Nutzung:
void wage_test() { wage worker; (amount&)worker = 100; // if you like this, can remove = operator worker = amount(105); // an alternative if the first one is too weird int value = (amount)worker; // getting amount is more clear }
Dies ist ein anderer Ansatz, bedeutet nicht, dass er gut oder schlecht ist, sondern anders.
quelle
Lassen Sie mich Ihnen eine zusätzliche Möglichkeit nennen, die am gewissenhaftesten erscheint.
Müssen lesen und ändern
Deklarieren Sie diese Variable einfach als öffentlich:
class Worker { public: int wage = 5000; } worker.wage = 8000; cout << worker.wage << endl;
Muss nur lesen
class Worker { int _wage = 5000; public: inline int wage() { return _wage; } } worker.wage = 8000; // error !! cout << worker.wage() << endl;
Der Nachteil dieses Ansatzes besteht darin, dass Sie den gesamten aufrufenden Code ändern müssen (dh Klammern hinzufügen), wenn Sie das Zugriffsmuster ändern möchten.
quelle
Variation von # 3, mir wurde gesagt, dass dies ein "fließender" Stil sein könnte
class foo { private: int bar; private: int narf; public: foo & bar(int); public: int bar(); public: foo & narf(int); public: int narf(); }; //multi set (get is as expected) foo f; f.bar(2).narf(3);
quelle