Gibt es hierfür ein Pattern?



  • Artchi schrieb:

    void setMaterial(Day &day, std::vector<std::pair<int, Color> &c)
    {
        for(int i=0; i<c.size(); i++)
            day.setMaterial(c[i].first, c[i].second);
    }
    

    Die Funktion sollte aber keine Memberfunktion von Day sein!

    Vielleicht stehe ich gerade etwas auf dem Schlauch, aber warum keine Memberfunktion?



  • dooooomi schrieb:

    Vielleicht stehe ich gerade etwas auf dem Schlauch, aber warum keine Memberfunktion?

    Weil es eine reine Bequemlichkeits-Funktion ist und weder Zugriff auf die Innereien der Klasse liefert noch das eigentliche Interface der Klasse wirklich erweitert.



  • Ah, richtig, danke. Etwas gewöhnungsbedürftig, aber an dem Argument ist natürlich was dran.



  • Richtig, man muß eigentlich immer nur schauen, ob eine Funktion Zugriff auf protected oder private Members benötigt. Wenn nicht, ist die Funktion eine Static-Member-Funktion oder besser eine freie Funktion.

    Komfort-Funktionen, die z.B. einfach nur mehrere public-Member-Funktionen "sammeln", auf jeden Fall als freie Funktion.

    Browser b("http://test.de");
    // ...
    b.clear();
    b.loadCurrentURL();
    

    Kann man so komfortabler machen:

    void reload(Browser &b)
    {
        b.clear();
        b.loadCurrentURL();
    }
    
    reload(b);
    


  • const_Ulli schrieb:

    @seldon: Deinen Vorschlag finde ich interessant 👍 ,

    aber warum ist in deinem Bespiel

    material_setter_t
    

    und

    material_setter
    

    gemischt (ich meine das _t ). War das ein versehen oder steckt da ein
    Hintergrund drin den ich nicht verstehe ?

    Gruß Ulli

    Ein Versehen. Ich hatte das erst als Template angelegt, weil aus dem ursprünglichen Posting die Typen von mDay und color[1-4] nicht hervorgingen, aber das wurde für ein Verdeutlichungsbeispiel zu unübersichtlich. Wenn man es als Template anlegt, ist eine Funktionsvorlage sinnvoll, um den Typen von mDay automatisch aufzulösen, also hatte ich da eine Trennung zwischen Funktionsvorlage material_setter und Typvorlage material_setter_t, und das _t habe ich versehentlich nicht überall entfernt, als ich mir der Einfachheit halber selbst Typnamen aus den Fingern gesogen habe.



  • Artchi schrieb:

    Richtig, man muß eigentlich immer nur schauen, ob eine Funktion Zugriff auf protected oder private Members benötigt. Wenn nicht, ist die Funktion eine Static-Member-Funktion oder besser eine freie Funktion.

    Tjoah, wenn das denn immer so klar wäre.

    Was wenn eine kleine Änderung dazu führt, dass die ehemals "macht gar keinen Sinn als Member"-Funktion auf einmal 100x performanter als doch-Member-Funktion implementiert werden kann?
    Klar, man kann die dann zu nem friend machen, aber das ist ja auch nicht schön.

    Ich will damit nicht grundsätzlich gegen freie Funktionen argumentieren. Nur... man sollte es sich schon gut überlegen. Und die Regel "wenns keine Memberfunktion sein muss, dann auch nicht machen" finde ich zu einfach.



  • Verstehe nicht so ganz was du mit der Performance meinst? Meinst du, wenn man direkt auf die privaten Member-Variablen zugreift?

    Aber private Memebers darf man doch eh nicht nach draußen geben. Also würde die Regel nicht greifen. Ist wohl eher theoretischer Fall.

    Gesetz den Fall, das man bemerkt, das man daraus eine Member-Funktion macht, weil es dadurch schneller wird. Was wäre dann das Problem die Komfortfunktion zu ändern?

    void reload(Browser &b)
    {
         // OLD:
         // b.clear(); 
         // b.loadCurrentURL();
    
         // NEW:
         b.reload();
    }
    

    Der Client-Code müsste also nicht mal zwangsweise geändert werden um die neue Member-Funktion zu nutzen, ohne die API-Kompatibilität zu brechen. Und der Compiler würde sicherlich die Komfortfunktion sowieso inline machen.

    Alles wieder in Butter! 😃 👍

    Klar, wenn man Einfluss auf Client-Code hat, könnte dieser natürlich direkt angepasst werden. Dann kann die freie Funktion gelöscht werden. Aber grundsätzlich sehe ich keinen Nachteil für spätere Erkenntnisse.

    Gibt es eigentlich in C++11 ein Deprecated-Tag? 😕



  • Pattern? schrieb:

    Ich will nun bestimmte Dinge berechnen, nachdem das LETZTE Mal setMaterial() aufgerufen wurde.

    Irgendwie fallen mir dazu Fluent-Interfaces ein (Die mir vor allen im C#-Umfeld untergekommen sind), die würden in etwa wie folgt aussehen (Handelt sich im wesentlichen um eine Factoryklasse):

    FluentTypname fluent;
    Typname t = fluent
      .SetMaterial(20, color1) // <-- Methode gibt die Instanz selbst zurück...
      .SetMaterial( 44, color2) // ... so das weitere Aufrufe möglich sind.
      .SetMaterial(100, color3)
      .SetMaterial(180, color4)
      .Generate(); // <-- Diese Methode gibt das fertige Element vom Typ "Typname" zurück.
    

    Ich selbst nutzte dies in C++ bisher bei der Definition von Rechten und Standardrollen einer Benutzerverwaltung, die Schnittstelle war so zumindest gut lesbar, auch wenn ich noch Schachtelungsebenen hatte...

    Bei mir sah das im wesentlichen dann so aus:

    CDefiniereDatenBenutzerverwaltung fluent;
    fluent.DefiniereRecht(enum-Wert) // <-- Gibt eine eigene Fluentschnittstelle für Rechte zurück.
      .Bezeichnung("...")
      .Beschreibung("...")
      .Standardrolle(enum-Wert)
      .Standardrolle(enum-Wert)
      .ErfordertRecht(enum-Wert)
      .Erzeuge() // Rückkehr zum ursprünglichen fluent-objekt...
    // ... Definition weiterer Rechte, Standardrollen etc.
    ;
    
    return fluent.Erzeuge(); // <-- In meinen Fall aber keine Rückgabe sondern Generierung der DB-Einträge...
    

    Dadurch das nur die Erzeugungsmethode die Definition abschließen (und prüfen) konnte war hier eine Sicherstellung der Reihenfolge möglich.



  • @asc
    das erzeuge() kannst du dir aber sparen, wie bereits vorher dargestellt wurde.

    In deinem Fall ginge das natürlich nicht mit dem destructor, da du den Rückgabewert brauchst, aber mir fallen spontan 2 alternativen ein:

    //beides nur ganz grob skizziert
    
    //Möglichkeit1: über den Constructor
    
    //der Rückgabewert von fluent.SetMaterial(...);
    class FluentCreator{
       //...
    }
    
    class Typname{
        public:
            Typname(FluentCreator const& fluent){
                m_member=fluent.erzeuge();//irgendwie so ;)
            }
    };
    
    //Möglichkeit 2: conversion operator
    //der Rückgabewert von fluent.SetMaterial(...);
    class FluentCreator{
        public:
            operator Typname(){
                return erzeuge();//auch irgendwie so
            } 
    };
    


  • otze schrieb:

    @asc
    das erzeuge() kannst du dir aber sparen, wie bereits vorher dargestellt wurde.

    Das erzeuge() ist aber gut für's Gewissen, weil dann eventuelle Exceptions nimmer aus nem Destruktor geflogen kommen 😉

    EDIT: OK, Blödsinn, vergiss es 🙂

    Das war bezogen auf den ursprünglichen Code von seldon.

    Deine Möglichkeit (1) finde ich im übrigen grässlich, weil sie Factory und Werkstück unnötigerweise extremst koppelt.

    Deine Möglichkeit (2) finde ist vermutlich OK (obwohl ich Konvertierungsoperatoren so überhaupt nicht mag, aber das is bloss ne persönliche Abneigung)



  • Pattern? schrieb:

    Hallo,

    ich habe eine Klasse, der man beliebig viele Winkel/Farb Paare hinzufügen kann:

    mDay->setMaterial(20, color1);
    mDay->setMaterial(44, color2);
    mDay->setMaterial(100, color3);
    mDay->setMaterial(180, color4);
    

    Ich will nun bestimmte Dinge berechnen, nachdem das LETZTE Mal setMaterial() aufgerufen wurde.
    Ich könnte natürlich ein endMaterial() anbieten, aber das ist irgendwie unelegant. Gibts da vielleicht irgend ein Pattern, mit dem ich das machen könnte?

    Wie wäre es mit:

    // Observer
    template<class T>
    class matobserver
    {
    matobserver(T &o) : m_o(o){ }
    ~matobserver(){ m_o.calc(); }
    
    private:
    T &m_o;
    };
    
    // deine Klasse
    class Mat
    {
    
    void calc()
    {
     // calc here
    }
    
    void setMaterial(...)
    {
     // ...
    }
    };
    
    int main()
    {
    
    Mat mat;
    
    { matobserver mo(mat);
    
    // INSERT MATERIALS HERE -->
    
    mat.setMaterial(/*...*/);
    mat.setMaterial(/*...*/);
    mat.setMaterial(/*...*/);
    mat.setMaterial(/*...*/);
    mat.setMaterial(/*...*/);
    mat.setMaterial(/*...*/);
    
    // <-- INSERT MATERIALS HERE
    }
    
    }
    

    ungetestet...



  • Artchi schrieb:

    Gibt es eigentlich in C++11 ein Deprecated-Tag? 😕

    Nein, aber Attribute - und die darf der Compiler frei erfinden. Ich warte nur darauf dass Compiler ein deprecated-Attribut einführen 🙂



  • Artchi schrieb:

    Richtig, man muß eigentlich immer nur schauen, ob eine Funktion Zugriff auf protected oder private Members benötigt. Wenn nicht, ist die Funktion eine Static-Member-Funktion oder besser eine freie Funktion.

    Komfort-Funktionen, die z.B. einfach nur mehrere public-Member-Funktionen "sammeln", auf jeden Fall als freie Funktion.

    Browser b("http://test.de");
    // ...
    b.clear();
    b.loadCurrentURL();
    

    Kann man so komfortabler machen:

    void reload(Browser &b)
    {
        b.clear();
        b.loadCurrentURL();
    }
    
    reload(b);
    

    Was ist der Vorteil, wenn ich reload(b) anstatt b.reload() schreibe?





  • Und was ist jetzt besser, wenn man alle möglichen Methoden, die zu einer Klasse gehören, raus nimmt und in einen Namespace steckt oder ganz ohne Namespace, damit sie garkeiner mehr findet? Dann hat man einen "Monolith Namespace".

    Im übrigen ist das für mich sowieso nicht das Problem bzw. die Lösung für Monoliths. Für micht sind die Problem-Klassen solche, die zuviel machen und nicht die, die viele Methoden haben. Z.B. ein GUIController der alle Elemente einer GUI enthält, sowas müsste man in mehrere sinnvolle Klassen aufteilen. Wenn man da nur alles in freie Funktionen steckt, wird das Design auch nicht besser, sondern nur die Methoden im GUIController weniger.



  • Hast du den Artikel überhaupt weiter als bis zur Überschrift gelesen?

    Abschnitt 3 zum Beispiel? Dick mit Überschrift:

    Membership Has Its Rewards -- and Its Costs

    3. Which ones should be members, and which should not? Why?

    Dann wärst du zum Beispiel über eine Fußnote auch an diesen Artikel gekommen:

    http://drdobbs.com/cpp/184401197



  • asdfghjklbn schrieb:

    Was ist der Vorteil, wenn ich reload(b) anstatt b.reload() schreibe?

    Stell dir vor, du steckst die reload(Browser&)-Funktion in einer separaten Headerdatei.

    Durch den Austausch der Header, kannst du ein anderes Verhalten für Reload erreichen, ohne die Browser-Klasse ändern oder davo erben zu müssen.

    Ist ein Beispiel, was eine Trennung ermöglicht.



  • Artchi schrieb:

    asdfghjklbn schrieb:

    Was ist der Vorteil, wenn ich reload(b) anstatt b.reload() schreibe?

    Stell dir vor, du steckst die reload(Browser&)-Funktion in einer separaten Headerdatei.

    Durch den Austausch der Header, kannst du ein anderes Verhalten für Reload erreichen, ohne die Browser-Klasse ändern oder davo erben zu müssen.

    Ist ein Beispiel, was eine Trennung ermöglicht.

    Dann muss reload aber die einzige Funktion in dem Header sein, sonst musst du ja alles andere in dem Austauch-Header und dem Original-Header doppelt haben.

    otze schrieb:

    Hast du den Artikel überhaupt weiter als bis zur Überschrift gelesen?

    Etwas weiter und ich fand ihn wahnsinnig anstrengend, bis er mal zu Punkt kommt. Ich wollte ihn überfliegen, aber das funktionierte nicht, dann kamen nur 100 Zeilen std::string und ich hatte keine Lust mehr. Mich interessiert auch nicht das man einen find-Algorithmen für andere Sachen verwenden kann, weil ich nicht sehe, wie ich einen "b.clear(); b.loadCurrentURL();" - Algorithmus wieder verwenden kann. Erklär doch mal mit eigenen Worten, welche Vorteile du konkret in einem reload(b) gegenüber b.reload() siehst.



  • asdfghjklbn schrieb:

    Dann muss reload aber die einzige Funktion in dem Header sein, sonst musst du ja alles andere in dem Austauch-Header und dem Original-Header doppelt haben.

    Richtig, so sieht es aus. Du mußt als User-Code nur einen anderen Header inkludieren. Die Klasse Browser mußt du nicht anfassen, besonders wichtig wenn sie nicht von dir ist.

    asdfghjklbn schrieb:

    Mich interessiert auch nicht das man einen find-Algorithmen für andere Sachen verwenden kann, weil ich nicht sehe, wie ich einen "b.clear(); b.loadCurrentURL();" - Algorithmus wieder verwenden kann. Erklär doch mal mit eigenen Worten, welche Vorteile du konkret in einem reload(b) gegenüber b.reload() siehst.

    Der find-Algo ist nur ein Beispiel. Kann auch das Reload als Beispiel herhalten.

    Es geht darum, das du als User einer Klasse nur die vorhandenen Member-Functions nutzen kannst. Du kannst die Klasse z.B. nur durch Vererbung verändern, obwohl du wahrscheinlich nur die Public Members nutzen willst.

    Also, es gibt kein Reload. Was machst du?
    Dabei willst du aber die Klasse verständlich halten. Das schaffst du nur, wenn du sie nicht mit Publics zumüllst.
    Wahrscheinlich will jemand ein anderes Reload-Verhalten haben? Er will nicht nur den flüchtigen Cache löschen, sondern auch den auf der Platte?
    Mann könnte also verschiedene Klassen ableiten oder die Klasse direkt erweitern und komplexer machen.

    Oder man lagert die Komfortfunktion in eine separate Funktion aus.

    // browser_helper.hpp
    #include <browser.hpp>
    
    void reload(Browser &b)
    {
       b.clear();
       b.loadCurrentURL();
    }
    
    // browser_helper2.hpp
    #include <browser.hpp>
    
    void reload(Browser &)b;
    {
       b.clearBrowserDir();
       b.clear();
       b.loadCurrentURL();
    }
    

    Ich brauche in meinem Code nur einen anderen Header inkludieren oder einfach nur die reload-Funktion selbst ändern. Aber die Browser-Klasse bleibt unangetastet! Ich kann mir dadurch z.B. das Rebuild des Browser-Projektes sparen. Vielleicht habe nicht mal die Sourcen zum Browser-Projekt? Und außerdem ist die Browser-Klasse weiterhin schlank geblieben: kein zweites Reload:

    class Browser
    {
      public:
         void clear();
         void clearBrowserDir();
         void loadCurrentURL();
    };
    
    // ohne freie Funktionen wird es komplex und der User
    // hat trotzdem keinen Vorteil
    class Browser
    {
      public:
         void clear();
         void clearBrowserDir();
         void loadCurrentURL();
         void reload();
         void clearBrowserDirAndReload();
    };
    

    Ein weiterer Vorteil ist noch, das die freien Funktionen gehindert werden Schindluder zu treiben und somit Laufzeitfehler minimiert werden, da sie nur auf definierte Public-Members zugreifen dürfen. Um so mehr Public-Members du aber hast, um so mehr mußt du aufpassen, was du mit den Protected- und Private-Members anstellst. Gerade private und protected sollen unnötigen Zugriff verhindern.



  • OK, wenn die Klasse nicht von einem selber ist und man eine Komfortfunktion braucht, kann man die als freie Funktion machen.

    Einen Browser würde ich aber auch anders machen. loadCurrentURL und reload sind irgendwie sowieso das gleiche. Ich würde glaub ich beide nicht haben, sondern einfach nur setURL(url). Wenn man die aktuelle url nochmal setzt, ist das reload. Das clear braucht man vorher nicht, weil der Browser vorher sowieso intern clear macht, wenn man eine url setzt, egal ob sie die alte oder eine neue ist. Wahrscheinlich würde ich clear nicht mal public machen. Braucht man das für irgendwas? Eigentlich braucht man nur eine Methode um einen neuen Tab zu öffnen. Ein clearCache würde ich anbieten, aber natürlich kein clearCacheAndDoSonstwas. Vielleicht entspricht mein Browser damit sogar noch dem Muster, dass meine Methoden alle auf private Sachen zugreifen, aber an sowas denke ich eigentlich nie. Wenn ich eine Klasse habe, die irgendeine komplexere Logik enthält, würde ich das in die Klasse machen, egal ob man das über public Methoden machen kann oder nicht, sondern einfach nur davon abhängig, ob die Logik zur Klasse gehört und aus Information Hiding Gründen da rein muss.


Anmelden zum Antworten