Designfrage



  • Ich habe hier mehrere unterschiedliche Wege das gleiche Problem zu lösen und mich würde mal interessieren, welchen dieser Wege ihr bevorzugt. (Die Funktion läuft einige Minuten und wird eventuell in einen eigenen Thread verlagert.)

    Der C Weg:

    struct state
    {
        vector<char>, int, string, ...;
    };
    
    void helperfunction1(state* s)
    {
    }
    
    void helperfunction2(state* s)
    {
    }
    
    void foo(int i, string s)
    {
        state s;
        s.i = i;
        s.s = s;
        helperfunction1(&s);
        helperfunction2(&s);
    }
    

    Konstruktor Deluxe:

    class foo
    {
        vector<char>, int, string, ...;
        public:
        foo(int i, string s)
        : i(i), s(s)
        {
            helperfunction1(&s);
            helperfunction2(&s);
        }
    
        void helperfunction1(state* s)
        {
        }
    
        void helperfunction2(state* s)
        {
        }
    };
    

    Run Funktion:

    class foo
    {
        vector<char>, int, string, ...;
        public:
        foo(int i, string s)
        : i(i), s(s)
        {
        }
    
        void run() // Parameter erst hier?
        {
            helperfunction1(&s);
            helperfunction2(&s);
        }
    
        void helperfunction1(state* s)
        {
        }
    
        void helperfunction2(state* s)
        {
        }
    };
    


  • In den letzten beiden Beispielen müssen natürlich keine Pointer mehr übergeben werden.


  • Mod

    Kommt ganz drauf an. Die Frage ist zu abstrakt gestellt.



  • Ich verwende in letzter Zeit immer öfter Method-Objects. Das wäre also Variante 2 bzw. 3.
    Wobei ich eine Run-Funktion verwende -- alles in den ctor zu packen kommt mir einfach irgendwie komisch vor.

    D.h. es gibt weiterhin die freie Funktion foo , die bloss mit einem (z.B. in einem anonymen Namespace definierten) Klasse Method-Object foo_method implementiert wird.

    Meine Method-Objects sind dabei allerdings im Normalfall nicht public, wodurch es auch mehr oder weniger egal ist ob sie jetzt alles im ctor machen oder ne Run-Funktion haben.

    Wobei ich SeppJ auf jeden Fall insofern Recht geben muss, als dass ich meine dass man ohne weitere Informationen nicht sagen kann ob hier ein Method-Object überhaupt angebracht ist oder nicht.

    z.B. könnte es sein dass helperfunction1 und/oder helperfunction2 auch für sich allein genommen Sinn machen. Dann wäre es vermutlich besser helperfunction1 und/oder helperfunction2 generisch zu implementieren und ihnen einen vernünftigen Namen zu verpassen. So dass man sie halt wiederverwenden kann.

    Bzw. wenn der "state" von helperfunction1 und helperfunction2 nicht modifiziert wird, dann besteht IMO auch kein besonders guter Grund ein Method-Object zu verwenden. Man könnte dann genau so gut state, helperfunction1 und helperfunction2 in einem anonymen Namespace definieren -- ohne Method-Object.

    Wichtig finde ich dabei hauptsächlich dass Dinge die für sich allein überhaupt keinen Sinn machen nicht öffentlich erreichbar sind.
    Ein Method-Object macht das ganze nur etwas übersichtlicher, dadurch dass man nicht überall einen State-Parameter rumreichen muss. Und es "dokumentiert" schön dass die (privaten!) Member-Funktionen des Method-Objects nicht unabhängig vom Method-Object verwendbar sind.



  • hustbaer schrieb:

    Ich verwende in letzter Zeit immer öfter Method-Objects.

    Kannst du ein zusammenhängendes Beispiel skizzieren?



  • Ein konkretes Beispiel oder bloss wie die Struktor davon aussieht?
    Für ein konkretes Beispiel müsste ich im Code nachsehen - bin aber gerade in Urlaub.

    Von der Struktur her sieht das so aus:

    namespace {
    
    class FooMethod
    {
    public:
        static int Run(int a, string b)
        {
            FooMethod m(a, move(b));
            return m.RunImpl();
        }
    
    private:
        FooMethod(int a, string b) { ... }
    
        int RunImpl()
        {
            PrepareSomething();
            DoSomething();
            int result = ComputeSomething();
            DisplayAccumulatedErrorMessages();
            return result;
        }
    
        // Diverse Hilfsfunktionen
    
        // Diverse Membervariablen (oft mutable state)
    };
    
    } // namespace
    
    int Foo(int a, string b)
    {
        return FooMethod::Run(a, move(b));
    }
    


  • Ok, dann ist der Aufbau vom Method Object selber klarer. Ich dachte, vielleicht sieht man beim Aufruf eher, warum das einen Vorteil bietet (keine Ahnung, z.B. eine Liste von Commands, macht mit einer statischen Methode aber natürlich keinen Sinn).
    Warum genau bist du zu der Struktur übergegangen, wo siehst du vor allem die Vorteile?



  • Vorteile:
    Dass man an die diversen Unterfunktionen nicht so viel State Variablen/Strukturen mitgeben muss sondern statt dessen einfach Membervariablen machen kann.

    Es ist dadurch auch sofort offensichtlich dass die diversen Hilfsfunktionen ausserhalb des Method-Objects keinen Sinn ergeben, und daher auch nicht von ausserhalb aufgerufen werden sollten (bzw. beim Method-Object kann man sie gar nicht von aussen aufrufen).

    Natürlich könnte man statt dessen auch ein .cpp File machen in dem man nur die Funktion Foo implementiert und sonst nichts, und die ganzen Memberfunktionen zu freien Funktionen in einem anonymen Namespace machen. Dann muss man aber eben alles was im Method-Object Membervariablen waren als Parameter mitgeben, was schnell unübersichtlich wird. Das Refactoring das diesen Umstand (Unübersichtlichkeit) behebt wäre alle Dinge die am mehrere Funktionen mitgegeben werden in eine struct zusammenzufassen. Dann kann man gleich noch aus der struct eine Klasse machen und die Funktionen zu Memberfunktionen denen das "this" implizit mitgegeben wird. Und dann hat man ein Method-Object.

    Weiters kann ein Method-Object natürlich auch Interfaces implementieren -- wobei ich das noch nicht wirklich verwendet habe (ausser IDisposable in C# weil ich lieber using() als finally{} schreibe).

    Wenn ich schreibe "öfters" meine ich auch nicht andauernd. Waren in den letzten 6 Monaten vielleicht 3 oder 4 Method-Objects die ich gebaut habe. Verglichen mit den genau 0 Method-Objects die ich in den Jahren davor gebaut habe ist es aber "immer öfter" 😉

    Mechanics schrieb:

    Ok, dann ist der Aufbau vom Method Object selber klarer. Ich dachte, vielleicht sieht man beim Aufruf eher, warum das einen Vorteil bietet (keine Ahnung, z.B. eine Liste von Commands, macht mit einer statischen Methode aber natürlich keinen Sinn).

    Falls du das meinst:

    class Foo
    {
    public:
        std::vector<Bar> parameters;
        std::vector<Baz> commands;
        // ...
    
        void Run() {}
    };
    
    void CodeThatsUsingFoo()
    {
        Foo f;
        f.parameters.push_back(...);
        f.parameters.push_back(...);
        f.commands.push_back(...);
        f.commands.push_back(...);
        // ...
        f.Run();
    }
    

    Dann weiss ich nicht ob ich das noch als Method-Object bezeichnen würde. Kann man aber natürlich so machen. Würde ich aber nur machen wenn es einen echten Vorteil bringt -- z.B. wenn die einzelnen Membervariablen von Foo teuer zu erzeugen und/oder zerstören sind, und man Foo manchmal mehrfach hintereinander verwenden wird.

    Ansonsten sehe ich das Method-Object wie gesagt eher als Implementierungsdetail - und mache es daher nicht public.



  • hustbaer schrieb:

    Vorteile:
    Dass man an die diversen Unterfunktionen nicht so viel State Variablen/Strukturen mitgeben muss sondern statt dessen einfach Membervariablen machen kann.

    Ok, dann würde ich aber wohl eine freie Funktion als public Schnittstelle anbieten und das Method Object dahinter in der cpp benutzen. Sonst hat man evtl. zu viele Implementierungsdetails im Header, find ich meist nicht gut.

    Schaut so als Pattern interessant genug aus, versuch mal dran zu denken, wenn ich mal wieder sowas ähnliches brauche.



  • Mechanics schrieb:

    Ok, dann würde ich aber wohl eine freie Funktion als public Schnittstelle anbieten und das Method Object dahinter in der cpp benutzen. Sonst hat man evtl. zu viele Implementierungsdetails im Header, find ich meist nicht gut.

    Ja, genau.
    Also eh so wie hier skizziert: https://www.c-plusplus.net/forum/p2460151#2460151


Anmelden zum Antworten