Wenn ich eine Klasse habe, die von einer anderen erbt, und nur diese Klasse muss eine bestimmte Variable verwenden, was ist besser? Soll die Variable in der Basisklasse "geschützt" sein, oder muss sie privat sein und ihr einen geschützten Getter geben?
Ich habe widersprüchliche Dinge gehört. Meine Lehrer haben mir gesagt, dass ich immer Getter benutzen soll, während andere Leute mir gesagt haben, dass die Verwendung von Getter auf jedem Level schlechtes Programmdesign zeigt. Was ist die wahre Antwort? Ich habe das Gefühl, beide sind unlogische Extreme.
Auch wenn Getter und Setter schlechte Programmgestaltung sind, warum ist das?
Gibt es irgendwelche Ressourcen, die mir mehr darüber beibringen, wie ich meinen Code strukturiere?
Benötigen Sie (oder nehmen Sie an, dass Sie in Zukunft etwas brauchen werden), um etwas anderes zu tun, als nur einen Wert zu lesen? Zum Beispiel: Assertions, Sperren oder machen das Lesen polymorph?
Ob Sie protected
verwenden oder nicht, ist vollständig orthogonal dazu.
BTW, verwaltete Sprachen wie C # und Java erfordern oft das Vorhandensein von Gettern, wobei "logisch" nur die gewöhnlichen Felder ausreichen würden, weil ihre UI-Design (und andere) Werkzeuge implementiert wurden, um reflection auf diese Weise. So scheint die Praxis, Getter übermäßig zu benutzen, das C ++ trotz der fehlenden Reflexion oder solcher Werkzeuge in C ++ "abgerakelt" zu haben.
protected
ist näher an public
als private
. Benutzer können eine abgeleitete Klasse erstellen, auf die geschützten Member zugreifen und sie ändern und ihre abgeleitete Instanz als Instanz der Basisklasse verwenden. Auf dieser Grundlage können Sie Ihre Entscheidung treffen. Wenn Sie möchten, dass ein Datenelement für die Außenwelt schreibgeschützt ist, benötigen Sie einen Getter, und daran ist kein Weg vorbei. Ein geschützter Getter (und vielleicht Setter) kann auch funktionieren.
Eine weitere Sache ist, dass Setter als Gateway zu Ihren Daten fungieren können. Sie können verwendet werden, um Bereiche zu validieren und bei Bedarf Ausnahmen auszulösen. Berücksichtigen Sie dies auch.
Da Sie auch angegeben haben, dass es für eine bestimmte abgeleitete Klasse verwendet wird, möchten Sie vielleicht die Klasse friend
erstellen. Dies kann oder auch nicht eine gute Idee sein, sollten Sie sorgfältig für und Nachteile bewerten.
Ich denke nicht, dass Getter und Setter im Allgemeinen ein schlechtes Design haben. Ich bin mir sicher, dass sie missbraucht werden können, wie fast jedes Idiom oder Muster. Verallgemeinern ist nie eine gute Idee. (1)
(1) Ja.
In den meisten Fällen zeigen Getter und Setter ein schlechtes Desing. Aber es gibt keine allgemeine Regel. Der Hauptgrund für die Verwendung von Getter und Setter sollte das Debuggen sein. Wenn Sie also von einer abgeleiteten Klasse auf ein Basisklassenmember zugreifen, haben Sie einen Punkt, an dem Sie einen Breakpoint setzen können, um Änderungen an diesem Member abzufangen.
Also sollten Sie sich anpassen. Wenn Sie planen, 2-3 Vererbungsebenen zu haben, ist es besser, mit geschützten Mitgliedern zu gehen, da es nicht so viele Orte gibt, die das Mitglied ändern kann. Wenn mehr verwendet wird, können geschützte Setter / Getter eine bessere Option sein - Sie möchten keine Haltepunkte in jeder Klasse festlegen, die möglicherweise ein Mitglied der Basisklasse ändern können.
Wenn auf das Element in der Basisklasse nicht außerhalb der abgeleiteten Klasse zugegriffen werden muss, müssen Sie protected
in der Basisklasse angeben. Das ist der Zweck von protected
access specifier.
Getter- und Setter-Methoden sind eine explizite Art zu sagen, dass diese Membervariable zur Verwendung verfügbar ist und normalerweise verwendet werden sollte, um das Member externen Entitäten zugänglich zu machen. Sie machen die Absicht klar, aber da auf Ihre Variablen nur in der abgeleiteten Klasse zugegriffen werden muss, drückt der Zugriffsbezeichner protected
access die Absicht bereits deutlich aus.
Was sind Klassen? Sammlungen von Daten oder Sammlungen von Verhalten ?
Natürlich sind sie beide. Aber stellen wir uns vor, wie Felder und Zugriffsmethoden (Getter und Setter) Ihnen ermöglichen, mit Daten und Verhaltensweisen zu arbeiten.
Felder
Zugriffsmethoden
Get
, Create
, Find
usw.) so clever wie Sie wollen. Sie definieren ein Verhalten. Tangente: Die Verkettung von Methoden ist ordentlich, weil Sie damit etwas erstellen können, das ein "Fluent Interface" .
Kapselung
Was immer du tust, du solltest dich an deine OO-Prinzipien erinnern. Verstoßen Sie nicht gegen Ihre Einkapselung .
Wenn Sie eine Klasse schreiben, die ihr gesamtes Verhalten einkapseln soll und dennoch ein Feld freilegt, haben Sie die Kapselung durchbrochen. Wenn Sie eine Klasse geschrieben haben, die Daten speichert, und bequeme Zuordnungs- / Datengenerierungsmuster aufweist, die sich den Methoden dieser Klasse gut zuordnen lassen, haben Sie Ihre Kapselung nicht durchbrochen.
Welcher Fall für Ihre Klasse zutrifft, hängt von der Ebene der Abstraktion ab, in der die Klasse arbeitet.
Wann sollten Sie jedes
verwenden?Sie haben beide Sinn, in bestimmten Kontexten zu verwenden.
Bei niedrigeren Codeebenen ist es sinnvoll, enger und enger mit Daten zu arbeiten. In diesen Fällen sollten Sie die leistungsfähigste und am meisten datengebundene Syntax verwenden, die Sie verwenden können. Verwenden Sie Felder .
Auf höheren Ebenen von Code ist es sinnvoll, enger mit Verhaltensweisen zu arbeiten. In diesen Fällen sollten Sie die flexibelste und am meisten verhaltensgebundene Syntax verwenden, die Sie verwenden können. Verwenden Sie Zugriffsmethoden . Oder, oft, verwenden Sie keine Accessoren . Verwenden Sie stattdessen Schnittstellen, Klassen und Methoden ohne Zugriff.
Im Zweifelsfall wähle ich Flexibilität gegenüber Leistung. Es ist schwierig, Leistungsengpässe in ganzen Programmen vorherzusagen, indem die Dinge auf dieser speziellen Detailebene untersucht werden. Wir sind wirklich schlecht darin, und deshalb gibt es Profiler. Es ist einfacher, einen Accessor zu einem Feld zu machen als umgekehrt. Wenn Sie vorsichtig und glücklich sind, haben Sie möglicherweise bereits Ihre Accessoren inline, die es dann einen strittigen Punkt machen würde.
Ihre geschützte und öffentliche Schnittstelle (Klassen, Mitglieder, Felder) sind Dinge, die Sie stabil halten müssen. Jedes Mal, wenn Sie Ihre geschützte und öffentliche Schnittstelle ändern, haben Sie die Möglichkeit, jeglichen davon abhängigen Code zu knacken.
Dies könnte eine Zeile Ihres eigenen Codes sein, den Sie kaputt machen. Es kann Hunderte von Klassen in Ihrer eigenen Codebasis geben. Wenn Sie Ihren Code etwas öffentlich geliefert haben, könnten Sie Tausende von Codezeilen von Hunderten von Programmierern, von denen Sie noch nie gehört haben, brechen und sich nie treffen.
Manchmal ist diese Pause notwendig und gut. Manchmal hätte es mit etwas Voraussicht vermieden werden können. Sich in die Angewohnheit zu versetzen, Ihre Gründe für den Wandel zu verstehen , ist der Kern für gutes Design.
Wenn Getter und Setter schlechtes Programmdesign sind, warum ist das so?
Getters und Setter geben Ihnen nur eine kleine Menge an Kapselung. Sie verstecken sich immer noch nicht viel vor Benutzern. Sie wissen immer noch, dass es in Ihrem Code ein Feld dieses Typs gibt (oder zumindest wissen, dass Sie vorgeben, dass es das gibt), und sie sind davon abhängig, dass es da ist. Wenn Sie die Implementierung Ihrer Klasse so geändert haben, dass dieses Feld nicht mehr benötigt wird, konnten Sie den Getter / Setter nicht entfernen, es sei denn, Sie wollten den gesamten abhängigen Code durchbrechen. Wenn Sie versuchen, die Unterbrechung zu vermeiden, müssen Sie diese Zugriffsmethoden noch funktionieren lassen und logischen Sinn machen, was schwierig sein könnte.
Manchmal ist das Freilegen eines Feldes (oder eines Getter / Setter) auch in Code mit hohem Level sinnvoll. Wenn dieses Feld für den Zugriff wichtig ist und niemals einen guten Grund hätte, den Namen oder Typ zu ändern (aus der Sicht eines Programmierers, der den Code verwendet), dann ist es vielleicht gut und am besten, es auf irgendeine Weise offenzulegen. p>
Manchmal ist das Umbrechen von Feldern in einem Getter / Setter sinnvoll. Wenn Sie Get / Setter verwenden, kann es einfacher sein, Protokollierung, Begrenzungsüberprüfung, Thread-Sperren / Semaphore und Debugger-Haltepunkte hinzuzufügen. In C ++ ist es auch einfacher, eine abstrakte Schnittstelle zu definieren, für die ein Getter / Setter vorhanden sein muss, als eine Schnittstelle zu definieren, für die ein Feld erforderlich ist.
Manchmal ist die direkte Belichtung eines Feldes und die Verwendung von Getter / Setter sinnvoll. Manchmal machen "Klassen", die ausschließlich aus Feldern bestehen, Sinn (man sollte stattdessen ein struct
verwenden). Dies wird am häufigsten bei Code auf sehr niedriger Ebene (z. B. Code, der Daten aus einer Datei abruft) oder innerhalb der Implementierung einer anderen Klasse (z. B. bei der Implementierung eines Algorithmus) verwendet. Oft werden Sie diese Klassen in anderen Klassen verstecken, damit Benutzer Ihres Codes sie nie sehen.
Meine Lehrer haben mir gesagt, dass ich immer Getter benutzen soll, während andere Leute mir gesagt haben, dass die Verwendung von Getter auf jedem Level ein schlechtes Programmdesign zeigt. Was ist die wahre Antwort? Ich habe das Gefühl, beide sind unlogische Extreme.
Pauschalaussagen haben oft die Wahrheit, aber Wahrheit ist selten binär.
Gewöhnen Sie sich an " warum? ". Gewöhne dich an, die Wahrheit für dich selbst zu beurteilen und Situationen in ihrem eigenen Kontext zu beurteilen. Manchmal ist das, was immer "das Beste" ist, in einer bestimmten Situation nicht wirklich am besten oder überhaupt wünschenswert.