Gibt es hierfür ein Pattern?





  • Seldons Ansatz gefällt schon sehr gut!

    Eine weitere Möglichkeit: Lass die aktuelle Member Funktion auf jeden Fall bestehen, aber gib noch eine Komfortfunktion hinzu, die einen Array oder Vector erwartet:

    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!



  • seldon schrieb:

    Etwas in dieser Art?

    class material_setter {
    // ...
    

    Gefährlich finde ich hier, dass die Berechnung irgendwann und ziemlich unerwartet stattfindet. z.B.

    {
    material_setter(mDay)
        ( 20, color1)
        ( 44, color2)
        (100, color3)
        (180, color4)
      ; 
    int i = mDay.get();
    }  // huch, wird erst hier berechnet.
    

    Der Setter sollte daher ein temporäres Objekt sein, dass sicher nach dem letztes set gelöscht wird.

    class Foo
    {
        class SetterProxy
        {
        public:
            SetterProxy(Foo& f) : foo_(&f) {}
            ~SetterProxy() 
            { 
                foo_->calc(); 
            }
            SetterProxy& set(int i) 
            { 
                foo_->setimpl(i); 
                return *this;
            }
        private:
            Foo* foo_;
        };
        friend class SetterProxy;
    
    public:
        Foo() : i_(0) {}
        SetterProxy set(int i)
        {
            setimpl(i);
            return SetterProxy(*this);
        }
        void calc()
        {
            i_ *= 2;
            std::cout << "calc ";
        }
        int get()
        {
            return i_;
        }
    private:
        void setimpl(int i)
        {
            i_ += i;
            std::cout << "set(" << i << ") ";
        }
    
        int i_;
    };
    
    int main()
    {
        Foo f;
    
        f.set(1).set(2).set(3);
        std::cout << f.get();
    }
    
    set(1) set(2) set(3) calc 12
    

    Wer es drauf anlegt kann zwar immer noch blödsinn anstellen aber es ist schonmal unwahrscheinlicher. Ich würde aber eh zunächst mal eine Lazy Evaluation wie von Michael vorgeschlagen vorziehen.



  • brotbernd schrieb:

    seldon schrieb:

    Etwas in dieser Art?

    class material_setter {
    // ...
    

    Gefährlich finde ich hier, dass die Berechnung irgendwann und ziemlich unerwartet stattfindet. z.B.

    {
    material_setter(mDay)
        ( 20, color1)
        ( 44, color2)
        (100, color3)
        (180, color4)
      ;   //// HIER ist die Lebenszeit bereits vorbei.
          //// Er speichert das konstruierte Objekt ja nirgendwo
    int i = mDay.get();
    }  // huch, wird erst hier berechnet.
    

    Nö, siehe Kommentar



  • @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



  • Autsch, ja sorry. Zu früh am morgen und zu flüchtig drauf geguckt. Es ist eigentlich das gleiche wie mein Beispiel, nur dass ich das Objekt beim ersten Aufruf in Foo erzeuge.



  • 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?




Anmelden zum Antworten