[Theoretische OOP] Wann private, wann public?

Crysis nerd

Freizeitschrauber(in)
Ich weiß, es klingt wie eine Anfängerfrage, aber ich möchte gerne mal zu einer Sache eure Meinung hören. Und zwar folgendes:
Überall wo man hingeht, wird man angebrüllt "MACH ALLE INSTANZ VARIABLEN PRIVATE MATHAFUCKAAAA!!!11".
Und ich sage: "Nein!" *haha*.

Okay, ernsthaft, was möchte ich?
Erinnern wir uns nochmal zurück: Was ist denn die grundlegende Idee der OOP (Objekt orientierten Programmierung)? Eine der Hauptideen ist auf jeden Fall die Kapselung. Von außen soll man nur das sehen, was für einen Notwendig ist. Die innere Logik bleibt gefällgst auch innen. Und vorallem wichtig beim Prinzip der Kapselung ist, dass nur meine eigenen Funktionen meine internen Daten verändern und ich mir so nichts kaputt mache.

Und da liegt ja auch schon mein Hauptanliegen: Ich habe (rein) interne Daten, damit sie von außen nicht kaputt gemacht werden können. Aber was heißt "kaputt"? Kaputt heißt nämlich, dass mein Objekt in einem inkosistenten Zustand vorliegt. Und was heißt inkonsistent?
Ein Beispiel:
Nehmen wir den std::string aus der C++ Standardbibliothek, eine normale String Klasse. Die Klasse speichert eine Zeichenkette variabler länge und hat einige nette Methoden um auf der Zeichenkette Zeugs zu machen. Aber es gibt auch die Methode size(). Diese gibt die Länge des Strings zurück. Wir möchten jetzt aber nicht die Länge jedes mal berechnen, indem wir die Zeichenkette durchlaufen und nach dem Nullbyte suchen, sondern wir möchten gerne die Länge zwischenspeichern in einer Membervariable und nur verändern, wenn wir die Zeichenkette verändern.
Ok klar? Hier stellen wir jetzt eine Vorraussetzung an die Beziehung der Variablen! Wir verlangen jetzt, dass der Wert, den wir in "length" speichern exakt der Länge der Zeichenkette entspricht. Wenn unsere Vorraussetzung erfüllt ist, ist unser Objekt in einem konsistenten Zustand. Falls allerdings diese Vorraussetzung nicht erfüllt ist, ist unser Objekt in einem inkosistenten Zustand und es wird sehr wahrscheinlich Fehler geben, wenn wir andere Methoden ausführen.
Wenn jetzt unsere Variable length public wäre, könnte ja ein Bösewicht des Weges kommen und diese verändern ohne die Zeichenkette zu verändern. D.h. er bringt unser Objekt in einen inkosistenten Zustand. Daher ist diese Variable eben nicht public.

Das betrifft viele Variablen, sodass viele private gemacht werden sollten. Auch bei Vorraussetzungen an Variablen an sich. Z.B. sollte eine Klasse, die einen Bruch repräsentiert, nie einen Nenner haben, der 0 ist. Das ist die Anforderung an die Klasse, und wenn der Nenner 0 ist, befindet sich das Objekt in einem inkosistenten Zustand.

ABER: Das gilt nicht für alles. Gucken wir uns z.B. eine Vector Klasse an, welche einen 3-dimensionalen Vektor speichern soll. Dazu speichern wir uns 3 Member (x, y und z) des Datentyps float. Supi. Und wenn man sich nach den meisten Tutorials und Lehrbüchern richtet, macht man jetzt die 3 Member private und bauen uns 3 Methoden getX() { return x; } getY()... und getZ()... und nochmal 3 Methoden setX(float xx) { x = xx; } usw..
Tolle Leistung. Haben wir 6 unnötige Methoden, die exakt nur eine Zuweisung tätigen. Jedes mal wenn wir auf die Variable zugreifen wollen oder sie setzen wollen haben wir ein Funktionsaufruf, ein JMP Befehl, einen neuen Stackframe erstellen, den Stackframe wieder abbauen (angenommen der Compiler ist nicht schlau).

Absolut aufwendig und unnötig. Warum sollte man das tun? Unsere Klasse kann durch die Member x,y und z NICHT in einen inkosistenten Zustand kommen. Warum sollten wir unsere Variablen dann schützen?
Wenn doch in den Settern nur exakt eine Zuweisung steht und in den Gettern nur genau eine return Anweisung, weiß man doch schon, dass etwas dumm gelaufen ist.


</Monolog>
Verpass ich was oder seid ihr meiner Meinung? Ich finde, dass sich kaum noch einer Gedanken macht, warum man Membervariablen eigentlich private macht und alle es nurnoch machen, weil man es überall hört. Oder habt ihr noch ein tolles Argument, um mich umzustimmen.
Wäre auf eure Meinung gespannt.

Grüße
 
Das ist so pauschal einfach nicht zu beantworten. :D

Dein Beispiel des Vektors ist ja erstmal ganz logisch, aber was ist wenn von der Position weitere Informationen abhängig sind? In diesen Fall hättest du das Problem, dass du wieder einen inkonsistenten Zustand bekommen würdest und du nichts dagegen machen kannst. ;-) Das mag dir beim Design der Klasse noch auffallen, aber was ist wenn du die Klasse nach Jahren erweiterst? Selbst wenn dir das auffällt, wäre es im besten Fall eine riesige Änderung deiner Anwendung. ;-)

Generell bin ich der Meinung man sollte jede bekannte Fehlerquelle ausschließen, und mit so einen Design gehst du ja wissentlich das Risiko ein. Das muss einen über kurz oder lang irgendwie wieder auf die Füße fallen. Das es bei der Optimierung oftmals zu solch einen Risiko kommt, steht natürlich auf der anderen Seite und muss im Einzelfall abgewogen werden. Generell halte ich es hingegen für Problematisch und ich habe schon einiges an schlechten Erfahrungen damit gemacht.
 
Ein Vektor ist jetzt ein Beispiel, wo es nicht unbedingt nötig wäre. Einige Operationen lassen sich hier ja auch als struct mit überladenen Operatoren abdecken, solche Implementierungen habe ich auch schon gesehen (ist dann fast C-like).

Ein Argument, was ich für Libs besonders wichtig finde: Wenn du direkt die Variable veröffentlichst, kannst du die Implementierung hinterher nicht mehr so einfach ändern. Nehmen wir beispielsweise diese Klasse hier

Code:
class Player {
public:
    int health;

// ...
};
Wenn der health Wert des Spielers jetzt aber durch Effekte (Health-Boost etc.) beeinflusst werden soll, ist diese Variante doof - denn du kannst es nicht ändern, ohne anderen Code anpassen zu müssen. Wenn die Klasse jedoch so aufgebaut ist

Code:
class Player {
private:
    int health;

public:
    int getHealth() {
       // do some magic (e.g. add health boost)
       // ...

       return health;
   }

// ...
};
dann kannst du die Berechnung des Health-Werts anpassen, ohne das Interface der Klasse zu modifizieren (bzw. den Client Code, der diese Klasse nutzt).

Die einfachen Methoden, die nur aus einem set oder return bestehen, werden idR eh inline gemacht, bei den heutigen Compiler sollte das also nicht ins Gewicht fallen (außerdem sollte man die 80-20-Regel beachten ^^)
 
mein gedanke ging auch in richtung von evandar: spätere änderungen laufen intern ab, die schnittstelle nach aussen bleibt gewahrt. sprich: du kannst intern an deiner klasse rumfuhrwerken, wie du möchtest, am nutzenden programm braucht man nichts ändern (also statt x = myVector.x muss man nach einer änderung, die konsistenzprobleme mit sich bringt plötzlich x = myVector.getX() schreiben). ob der umstand mit gettern und settern einem zum damaligen zeitpunkt schlau oder performant vorkam - oder eben nicht, hier zeigt sich dann, dass es vorrausschauender gewesen wäre, doch nen getter zu nutzen.

mMn gilt sowas aber weniger bei kleinen self-made projekten. eher schon für größere geschichten, wo dann mehrere programmierer an einem projekt arbeiten. ein punkt, den ich selbst so auch noch nie erlebt habe und daher bestenfalls verständlich erahnen kann ^^ aber hier werden eben gewissen klassen mit ihren merkmalen erdacht und für die benutzung untereinander die schnittstellen erdacht. ging man da nun bei ersten konzepten von einem simplen public zugriff aus und muss das aber später revidieren und auf private mit gettern und settern umbauen, dann haben all die anderen entwickler anderer programmteile/klassen dann das problem, alle aufrufe rauszufummeln und zu ändern. hätte man hier von anfang an "stupide" auf getter usw gesetzt, würde dies wegfallen.

einziger knackpunkt bliebe: wenn die erdachte schnittstelle für die neue funktionalität nicht mehr ausreicht. aber ich glaube, das ließe sich dann notfalls auch noch besser gestalten, wie dieser "public/private switch".

und auch wenn ich es nur für größere projekte als wirklich sinnvoll erachte, so ist es wohl besser, sich sowas gleich anzugewöhnen - und der eigene teich ist zum üben ja immernoch der schönste ^^

ps: ich seh grad, bingo hat wohl auch dieses argument :D
 
Sehr tolle Antworten, Danke!

Ihr argumentiert ja quasi alle in eine Richtung... und zwar an die Wahrung eines Interfaces und somit eine einfachere spätere Änderung der Interna. Daran hab ich natürlich viel zu wenig gedacht. Das ist schon ein sehr guter Grund. Letztendlich bleibt es dann im Einzelfall dem Programmierer überlassen, wie er es macht.
Um nochmal auf meine Vektor-Klasse zurückzukommen: Natürlich kann man auch nach 5 Monaten merken, dass man gerne die Länge des Vektors immer schon vorberechnet haben möchte und nicht immer on the fly berechnen. Dann wäre es durchaus ein Problem, wenn man die 3 Komponenten public macht. Aber nicht zu vernachlässigen ist natürlich der Performanceunterschied, wenn man mal von Compileroptimierungen absieht. Die "alles private" Methode erinnert mich hin und wieder an dieses Bild: http://asset-c.soup.io/asset/6130/3734_cd96.jpeg Und zwar meine ich den Abschnitt mit "Code written by a Large Company", wo alles mit unnötig vielen Wrapperklassen, Factories usw gemacht wird.
Aber wie schon gesagt: Ich denke wohl, dass euer Argument echt wichtig ist, aber in bestimmten Fällen es trotzdem sinnvoll ist, public Member zu verwenden.

Dazu: Wird Zeit, dass endlich ein gutes Properties Proposal für C++17 rauskommt *_*


Grüße,
Lukas
 
Um noch mal auf dein Beispiel zu kommen. Ich würde in solchen Fällen wohl zwei teilen. Eine Klasse erstellen das du für die Performanz intern brauchst und eines für extern. Sind wir mal ehrlich, in einen Scene Graphen fällt es faktisch nicht auf wenn ich über Get/Set Funktionen gehen muss (auch Properties sind ja eigentlich nichts anders, auch wenn ich sie nicht mehr missen möchte ;) ) da die Aufrufe doch überschaubar sind. Wenn ich dann aber Intern große Mengen bearbeiten muss, macht das aber dennoch einen Unterschied.

Daher mein bescheidender Vorschlag, Interface sicher halten, intern (aber auch nur da) optimieren wo es notwendig ist. Ach ja und schreib es dir auf, solche Feinheiten vergisst man gerne sehr schnell.
 
Die Wahrung des Interfaces ist wichtig! Es ist verdammt doof wegen einer kleinen Änderunge 250000 andere Klassen auch anfassen zu müssen :ugly:

C++ macht es da unnötig kompliziert, in C# ist das deutlich einfacher...

Code:
class player
{
 public int leben {get;set;}
}
kann ich ändern zu
Code:
class player
{
 public int leben
 {
   get{return _somePrivateThing.value;}
   set
   {
       _somePrivateThing.value=value;
   }
}
Beide Klassen haben das selbe Interface! Das macht es einfacher zu Anfang den Code kurz und simpel zu halten
und später erst zu ändern.
 
Ob ich jetzt eine Property oder zwei Funktionen für getter und setter habe ist vom Code her so ziemlich das gleiche, sofern man mehr als nur ein simples set/get macht. Properties an sich finde ich aber trotzdem cooler ;)
 
Ich muss Bingo da völlig recht geben. Es ist für das Interface völlig schnuppe, wie es aussieht, Hauptsache es ändert sich nicht. ;)
 
Die Hauptgründe für so etwas wie das Beispiel "Code written by a Large Company" sind zum einen, das manchmal mehrere Dutzend Programmierer an eine Projekt arbeiten und Daten hier nur noch über generische Interfaces sinnvoll ausgetauscht werden können.
Da hat niemand die Zeit, sich mit Implementierungsdetails zu beschäftigen, auch wenn die Performance etwas leidet, und die IDE's sorgen dafür, dass man sich trotz der schwer lesbaren Methodenaufrufe nicht total verirrt.

Zum anderen ist es fast unmöglich, Kernkomponenten später noch einmal auszuwechseln, wenn du diese nicht über vernünftig abstrahierte Schnitstellen ansprechen kannst.
In manchen Firmen arbeiten heute noch jahrzehnte alte Großrechner (z.B. mit BS2000) und und der Aufwand, diese auszuwechseln, ist immens, weil die damaligen Programmierer nicht mehr aufzutreiben sind und niemand den Code und dessen Abhängigkeiten komplett versteht.

Wie generisch gearbeitet wird, sollte jedoch am Einzelfall entschieden und ein guter Softwarearchitekt weiß um die Vor- und Nachteile und trifft hoffentlich die richtige Entscheidung :ugly:.

@ Crysis nerd
Es steht dir völlig frei, deine Klassen so zu gestalten wie du möchtest und bei kleinen "Spaßprojekten" mit ein paar Einwegklassen kann ich deinen Standpunkt nachvollziehen. Je nachdem, wo du später mal arbeitest, ist es aber auch nicht blöd, Prinzipien wie "segregation of concern", "encapsulation" etc. ausprobiert und geübt zu haben.
Ein durchschnittlicher, aktueller Compiler sollte ohnehin in der Lage sein, solche Einzeiler zur Laufzeit wegzuomptimieren. Diese sind letztendlich auch als Hilfestellung für die Programmierer zu sehen ;).
 
Sorry, dass ich erst so spät antworte!

Ich finde das ganze hier recht interessant, aber letztendlich läuft es immer wieder darauf hinaus ein Interface zu wahren. Das ist eine absolut wichtige Sache, das stimmt schon.
Und was properties angeht: Syntax Zucker ist toll! Ich hätte es auch gerne bald in C++... C++ ist doch sowieso die Syntaxzucker sprache schlechthin (mehr oder weniger).

Grüße
 
Naja ich händle es so, das die Variablen den Gültigkeitsbereich haben, den sie brauchen. Ich kann mir kein Senario vorstellen, wo ich eine öffentliche Variable brauche. Zum Thema Stack, es spricht ja nichts dagegen die getter Variable in Performance kritischen Bereichen in einer anderen Klasse Zwischen zu speichern. Ich stelle es mir auch schwierig vor Threadsafe zu bleiben, wenn eine Variable öffentlich ist kann jedes beliebige Thread darin rum pfuschen, mit einem Getter kann man leicht zusätzliche Logik dazu implementieren, die die Variable lockt.
 
Zurück