Klassen ohne getter/setter?



  • Was reicht ihr weiter (in einem C+-Programm): einen string oder einen char-array? Einen vector oder die interne Repraesentation davon?

    Im Uebrigen stammt die Argumentation, mit der scrub konfrontiert wurde in erster Linie von mir.



  • Was reicht ihr weiter (in einem C+-Programm): einen string oder einen char-array? Einen vector oder die interne Repraesentation davon?

    Versteh den Vergleich nicht. Vector und String sind Typen, richtig. Aber diese Typen werden dadurch "besonders" das sie bestimmte Eigenschaften haben. Und diese Eigenschaften sind vielfältig: Einzelne Elemente, Anzahl Elemente, Reservierte Elemente usw. So, und wie komme ich an diese Eigenschaften ran? Über getter und setter (nur heißen die nicht so, Namen sind Schall und Rauch):

    string str("Hallo");
    char a = str.at(0);   // Getter auf ein Element 0, heißt nur nicht get()!
    str.at(0) = 'B';      // Setter auf ein Element 0!
    str.c_str();       // Getter auf alle Elemente
    size_t s = str.size();  // Getter auf Eigenschaft Länge, heißt nur nicht get_size()
    str.resize(100); // Setter auf Eigenschaft Länge, heißt nur nicht set_size()
    

    Natürlich übergebe ich str an eine Funktion, wenn sie einen string haben will. Aber das bestimme nicht ich, sondern die Funktion, die ich benutzen will.

    Und genau das gleich wie Widget: ich habe Widget, das reiche ich weiter, wenn jemand ein Widget haben will. Aber was ist wenn er die Caption oder die XY-Pos des Widgets haben will?

    Also wirklich schlau werde ich aus dem Konzept "keine Getter/Setter" nicht. Schön das ich String- oder Widget-Objekte weiter reiche. Macht man ja auch und spricht nichts dagegen. Aber gaaanz am Ende muß man ja auf die Eigenschaften eines Objektes zugreifen. Wie soll das ohne Getter/Setter gehen? Oder geht es am Ende doch nur um die Namesgebung einer Member-Funktion? 😃



  • Im Uebrigen stammt die Argumentation, mit der scrub konfrontiert wurde in erster Linie von mir.

    Dann bin ich jetzt mal auf die Argumentation gespannt, warum ein Setter keine Prüfungen vornehmen darf. Die Argumentation des Getters und deren Objekte ist ja schon recht verdreht (siehe Artchi mit der Ausführung der Getter beim String Objekt), so das die Argumentation für Setter sicherlich sehr interessant wird 🤡



  • Dann bin ich jetzt mal auf die Argumentation gespannt, warum ein Setter keine Prüfungen vornehmen darf.

    Das ist absoluter Quatsch. Natürlich kann ein Setter Gültigkeitsprüfungen übernehmen. Nur weil er einfach nur Setter heisst, muss er ja nicht nur das tun. Da gibt es vielleicht Leute, die das so für sich festlegen, und dann als goldene Regel verkaufen wollen.



  • Also erstmal ich verstehe die ganze Diskussion ueberhaupt nicht. Was haben die Leute nur gegen getter/setter, bis auf tipparbeit ist es genau gleich wie direkt auf Attribute zugreifen, aber mit wesentlichen Vorteilen.

    Man sollte eher einen Artikel schreiben das man immer setter/getter verwenden sollte und wie man in C++ die Perfomance steigern kann, bzw. wie man keine Perfomance-Einbußen hat, wenn man getter/setter verwendet.

    Das ist ganz sicher kein Ideal-Beispiel, aber ich denke, ein paar wesentliche Punkte sind drin: Objekt gibt nur nötigste Informationen raus (Datei gibt nur den Inhalt, nicht Namen oder Pfad), Verantwortung liegt so hoch wie möglich (Dateimanager ist für's Datei-verschieben zuständig, nicht die Datei) und differenziertere Methoden (clear, seek, write vs setContent).

    Die Aufgabe einer Datei ist es aber ihren Pfad und Namen zu kennen. Das was du beschrieben hast sind die Daten einer Datei und nicht die Datei selbst.

    Eine Datei stellt nunmal eine Verbindung her zwischen einem Bezeichner (Pfad+Name) und den Daten selber. Ob sich eine Datei dabei selbst loeschen/kopieren/unbennen kann, ist eine andere Diskussion.



  • DEvent schrieb:

    Man sollte eher einen Artikel schreiben das man immer setter/getter verwenden sollte und wie man in C++ die Perfomance steigern kann, bzw. wie man keine Perfomance-Einbußen hat, wenn man getter/setter verwendet.

    Es geht doch gar nicht um Performance, sondern um stilistische Vor-/Nachteile, z.B. wie gut der Code wartbar, erweiterbar oder die Implementation austauschbar ist.

    DEvent schrieb:

    Die Aufgabe einer Datei ist es aber ihren Pfad und Namen zu kennen. Das was du beschrieben hast sind die Daten einer Datei und nicht die Datei selbst.

    Wir scheinen verschiedene Auffassungen von Aufgaben von Objekten zu haben. Es ist meiner Meinung nach ungünstig, jedes Objekt als Eigenschaften-Container zu betrachten und sich zu fragen: "Was hat das Objekt?"; und das, was das Objekt hat (seine Eigenschaften), kann man dann auslesen und/oder setzen.
    Es ist doch imho erstrebenswerter, sich zu fragen, welche Aufgabe ein Objekt hat, was es also kann. Man steuert damit dann auf eine Schnittstelle zu, die definiert, zu was das Objekt in der Lage ist, und nicht, welche Daten es enthält. Denn das kann uns als Benutzer des Objekts doch völlig egal sein.

    Das ist ja auch das tolle an der Trennung von View und Datenmodell: Es ist uns völlig egal, welche Daten das Modell hat, solange es unsere Befehle befolgt und die richtigen Antworten gibt. Sicher ist das aber oft ein sehr schmaler Grad zwischen Anforderungen und Daten-haben.

    Was ich oft sehe, wie die Trennung von View und Datenmodell missbraucht wird: Das Modell dient nur als Container, dient als Getter/Setter-Schnittstelle für die dahinterstehenden Daten. Soll dann aus dem View ein Datensatz irgendwo in das Datenmodell eingefügt werden, sieht das meist so aus: DataModel::Instance()->getThis()->getThat()->getDataSet()->insertData( ... ); . Das ist imo aber völlig schwachsinnig, weil man damit eine bestimmte Implementierung auf der Modell-Seite voraussetzt. Es wird halt das Modell nur als Container angesehen und wird nur benutzt, um auf die dahinterliegenden Datenstrukturen zuzugreifen. Eigentlich sollte es aber so sein, dass das View die dahinterliegenden Datenstrukturen gar nicht kennen sollte oder sogar darf, weil das eben ein Implementierungsdetail ist.
    Wenn wir unser Modell aber so modellieren, dass es bestimmte Anforderungen zu erfüllen hat, sieht das ganze schon anders aus. Als View haben wir dann einfach nur eine Schnittstelle, nämlich die Methoden vom Modell und nix dahinter. Da unser Modell die Anforderungen zu erfüllen hat, einen Datensatz zu irgendwas hinzuzufügen, muss der Aufruf so aussehen: DataModel::Instance()->insertThisThatData( ... ); .

    Das hat auch eine Menge Vorteile, es ist viel flexibler und sicherer. Sicherer ist es, da das gesamte Datenmodell nur einen einzigen Eingang hat, nämlich seine eigenen Methoden. Dadurch kann man Inkonsistenzen vermeiden, weil man den absoluten Überblick hat, wo genau Benutzerdaten reinkommen. Mit einem Modell als Container hast du unübersichtlich viele Interaktionspunkte, quer verteilt über das ganze Modell.
    Flexibler ist es, weil das Austauschen des Modells absolut trivial ist. Es könnten zwei Modelle völlig unabhängig voneinander entwickelt werden, die im Inneren eine völlig unterschiedliche Datenstruktur haben, sie müssen eben nur die _eine_ Schnittstelle erfüllen. Du könntest das Modell sogar als Internet-Service implementieren, die Schnittstelle mit dem das View interagiert, macht einfach ein paar RPCs.



  • Aus welchem Grund sollte es egal sein welche Daten ein Objekt enthält.Das kann bei manchen Klassen zutreffen, das dir die Daten egal sein können, weil du sie nicht zur weiteren Interaktion benötigst. Sobald wir aber konkret die Daten des Objektes benötigen brauchen wir Zugriff über einen Getter.
    Das

    DataModel::Instance()->insertThisThatData( ... );
    

    ist auch nur ein gewöhnlicher Setter. Ob du nun über den Setter deine komplette Datenstruktur füllst oder aber nur eine Member ist dabei irrelevant.



  • phloxxx schrieb:

    Aus welchem Grund sollte es egal sein welche Daten ein Objekt enthält.Das kann bei manchen Klassen zutreffen, das dir die Daten egal sein können, weil du sie nicht zur weiteren Interaktion benötigst. Sobald wir aber konkret die Daten des Objektes benötigen brauchen wir Zugriff über einen Getter.

    Wenn du konkrete Daten brauchst, kannst du immer noch eine Schnittstellen-Struktur entwerfen, die alle benötigten Daten trägt. Man sollte die Schnittstellen-Daten-Struktur aber nicht davon abhängig machen, welche Repräsentation der Daten im inneren des Modells benutzt wird. Denn das ist die Sache des Modells und nicht der Schnittstelle.

    phloxxx schrieb:

    Das

    DataModel::Instance()->insertThisThatData( ... );
    

    ist auch nur ein gewöhnlicher Setter. Ob du nun über den Setter deine komplette Datenstruktur füllst oder aber nur eine Member ist dabei irrelevant.

    Hast du den Unterschied zu dem Aufruf von DataModel::Instance()->getThis()->getThat()->getDataSet()->insertData( ... ); verstanden?



  • Sieht mir sehr danach aus, dass C++ Properties als Sprachfeature fehlen?!

    MfG SideWinder



  • Huch, woher hast du das jetzt abgeleitet?



  • Ich erwähne dazu mal ein Designprinzip namens Law of Demeter, das besagt, dass man nur Nachrichten an Objekte senden darf (sollte), die man besitzt, kennt (als Referenz hält) oder selbst erzeugt hat, nicht aber an solche, die man von anderen Objekten lediglich angefordert hat. Sowas ist also verboten:

    obj->getFoo()->doSomething();
    

    Das läuft auch unter dem Namen "Tell, don't ask". Sag dem Objekt, was es tun soll; lass dir nicht irgendwelche Interna geben und manipuliere diese.



  • SideWinder schrieb:

    Sieht mir sehr danach aus, dass C++ Properties als Sprachfeature fehlen?!

    MfG SideWinder

    👎 Du willst doch nur einen Flamewar provozieren. ⚠



  • Wir scheinen verschiedene Auffassungen von Aufgaben von Objekten zu haben. Es ist meiner Meinung nach ungünstig, jedes Objekt als Eigenschaften-Container zu betrachten und sich zu fragen: "Was hat das Objekt?"; und das, was das Objekt hat (seine Eigenschaften), kann man dann auslesen und/oder setzen.
    Es ist doch imho erstrebenswerter, sich zu fragen, welche Aufgabe ein Objekt hat, was es also kann. Man steuert damit dann auf eine Schnittstelle zu, die definiert, zu was das Objekt in der Lage ist, und nicht, welche Daten es enthält. Denn das kann uns als Benutzer des Objekts doch völlig egal sein.

    Ich stimm dir doch zu, allerdings hast du mit deinem "Datei" eher die Daten beschrieben, nicht eine Datei. Deine Datei hat nur Methoden um auf die Daten zuzugreifen, aber nichts um auf die Datei zuzugreifen.

    Egal ist eh OT.

    Es geht doch gar nicht um Performance, sondern um stilistische Vor-/Nachteile, z.B. wie gut der Code wartbar, erweiterbar oder die Implementation austauschbar ist.

    Dann verstehe ich die Diskussion erst recht nicht.

    Geht es darum statt getter/setter direkt auf die Attribute zuzugreifen?

    Denn getter/setter benoetigst du auf jeden Fall, und wenn du sie statt setXXX/getXXX eben read(), write() und seek() nennst. Wenn du das Kind Max statt Alex nennst, bleibst es doch ein Kind. 😉

    Edit:

    Hast du den Unterschied zu dem Aufruf von DataModel::Instance()->getThis()->getThat()->getDataSet()->insertData( ... ); verstanden?

    Ah darum gehts? Dann heist der Artikel: Abstraktion von Internen Strukturen und deren Vorteil vs direkten Zugriff auf alles?



  • Zitat:
    Hast du den Unterschied zu dem Aufruf von DataModel::Instance()->getThis()->getThat()->getDataSet()->insertData( ... ); verstanden?

    Ah darum gehts? Dann heist der Artikel: Abstraktion von Internen Strukturen und deren Vorteil vs direkten Zugriff auf alles?

    Dann geht es doch aber nicht generell um getter/setter? Sondern darum, ob ich mir eine Abhängigkeit in meinem Code aufbaue, oder durch eine betsimmte Methode den get-get-Strang verstecke?

    Also, irgendwie stehe ich immer noch im Regen. Zuerst kristalisierte sich mehr heraus, das es an der Namensgebung liegt (setSize vs. resize) obwohl es in der Implementierung gleich ist. Doch jetzt gehts anscheinend "nur" um die zu vermeidende Abhängigkeit? (was Sinn macht)

    Entweder ist jemand so nett und schreibt einen schönen Artikel darüber, wo wir aufgeklärt werden, oder ich sollte in Zukunft so weiter machen ohne Zweifel ("Oh nein, getter/setter sind doch angeblich schlecht, was mache ich jetzt?"). 🙂



  • DEvent schrieb:

    Badestrand schrieb:

    Es geht doch gar nicht um Performance, sondern um stilistische Vor-/Nachteile, z.B. wie gut der Code wartbar, erweiterbar oder die Implementation austauschbar ist.

    Dann verstehe ich die Diskussion erst recht nicht.

    Also ich denke nicht, dass es generell bei Getter/Setter vs anders um Performance geht, das dürfte sich wahrscheinlich ziemlich die Waage halten.

    DEvent schrieb:

    Denn getter/setter benoetigst du auf jeden Fall, und wenn du sie statt setXXX/getXXX eben read(), write() und seek() nennst. Wenn du das Kind Max statt Alex nennst, bleibst es doch ein Kind. 😉

    Hehe, das ist wahr 🙂 Naja, es ist imo ein wenig schwierig zu definieren, was nun ein Getter/Setter ist. Wenn Getter einfach alle Methoden sind, mit denen man sich Informationen aus dem Objekt holt und Setter alle befehlsartigen Methoden sind, dann sind sie definitiv unverzichtbar, Objekte könnten ja sonst gar nicht kommunizieren.
    Ich verstehe unter Gettern und Settern aber eher eine spezielle Art von Methoden, ich will's mal versuchen darzustellen:

    class Sonstwas
    {
    public:
        void SetFoo( Foo* p ) {ptr=p;}
        Foo* GetFoo()         {return ptr;}
    
    private:
        Foo* ptr;
    }
    

    Etwa so halt, wie Artchi das mit dem pModel bei MVC skizziert hatte; man hat ein Objekt und setzt ihm ein bestimmtes Objekt rein oder holt es heraus. Oder auch Klassen, bei denen quasi alle Attribute abfragbar und setzbar sind:

    class Sonstwas
    {
    public:
        // Hier Getter und Setter für jedes Attribut.
    private:
        int attr1;
        str attr2;
        foo attr3;
    }
    

    DEvent schrieb:

    Ah darum gehts? Dann heißt der Artikel: Abstraktion von Internen Strukturen und deren Vorteil vs direkten Zugriff auf alles?

    Vielleicht, ja 😃 Nein, aber genau solche Aufruf-Kaskaden bekommt man oft, wenn man mit obigen Gettern/Settern arbeitet.

    Tut mir Leid, wenn ich anscheinend ein wenig am Thema vorbeigeredet hab 🙂



  • PS: Was wären für euch denn Methoden, die weder Getter noch Setter sind?



  • Badestrand schrieb:

    PS: Was wären für euch denn Methoden, die weder Getter noch Setter sind?

    Aktionen. foo.do_something();



  • Etwa so halt, wie Artchi das mit dem pModel bei MVC skizziert hatte; man hat ein Objekt und setzt ihm ein bestimmtes Objekt rein oder holt es heraus.

    Und wie soll das Widget das Model kennen? Es geht dabei nicht darum, das Model zu verwalten, sondern nur zu kennen. Oder wie stellt ihr Verbindungen zu Objekten her?

    Am Ende geht es hier anscheinend nur darum, wie die Funktion heißt? Soll heißen, die Anti-Setter-Getter-Fraktion stört sich nur am Funktionsnamen? Denn vorallem bei OOD kann ich nie sagen, was hinter setX wirklich passiert. Wird tatsächlich nur ein Attribut neu gesetzt? Oder passiert da vielleicht nicht doch mehr?

    Beispiel MVC:

    // Pseudocode:
    
    class Widget
    {
          Model *model;
    public:
          void setModel(Model *m)   // evil?
          {
               model = m;
               model->addObserver(this);
          }
    
          void observe(Model *m)   // besser weil kein setter-Name???
          {
               model = m;  // set Model!
               model->addObserver(this);
          }
    
          ~Widget()
          {
              model->removeObserver(this);  // wie sonst, wenn es kein set model gibt?
          }
    };
    

    Klar, observe() als Funktionsname ist nicht schlecht. Aber die Implementierung ist in beiden Fällen gleich. Man kann also gerne darüber diskutieren, ob man immer den richtigen Namen für eine FUnktion wählt, aber das model = m; schlecht sein soll, kann ich nicht verstehen!!! Ich muß doch irgendwo mir meine Beziehungen merken dürfen, um mit ihnen zu aggieren (siehe z.B. Destruktor!).



  • Das Problem mit getter/setter ist wie mit goto. Zuviel verwenden ist furchtbar aber gleich garnicht verwenden ist mindestens genauso furchtbar.

    Getter/Setter werden liebend gerne für alle internen Variablen genommen aber nur ein Bruchteil des internen States hat nach aussen zu gelangen (aber dieser Bruchteil muss nach aussen).



  • Artchi schrieb:

    Etwa so halt, wie Artchi das mit dem pModel bei MVC skizziert hatte; man hat ein Objekt und setzt ihm ein bestimmtes Objekt rein oder holt es heraus.

    Und wie soll das Widget das Model kennen? Es geht dabei nicht darum, das Model zu verwalten, sondern nur zu kennen. Oder wie stellt ihr Verbindungen zu Objekten her?

    Im Konstruktor.


Anmelden zum Antworten