Akzeptanz unterschiedlicher Proxys für Klasse



  • Eisflamme schrieb:

    Da kann ich immer noch nicht an eine SomeUIClass SomeClass oder LoggedSomeClass übergeben.

    SomeClass = LoggedSomeClass mit l = new Logger(), dem Nulllogger



  • Okay, ich versuche es Mal anders zu erklären...

    SomeClass soll nicht geändert werden dadurch, dass es einen SomeClassProxy braucht. SomeClassProxy fügt Funktionen hinzu, die aber heterogener sind als ein einfacher Logger.

    Würde ich SomeClass dadurch erweiterbar machen, dass ich dort für jede Funktion einen vector von std::function machen würde, damit das manipulierbar ist, hätte ich ein unglaubliches Gefrickel, um meinen "Proxy" damit zu simulieren. Ich glaube nicht, dass Du das wirklich empfehlen willst.

    Überhaupt sehe ich keine Nicht-Frickel-Lösung, die intrusiv ist, d.h. SomeClass soll bestehen bleiben und SomeClassProxy soll Funktionen hinzufügen bzw. Aufrufe abändern (Proxy eben), letztendlich aber ein bereits bestehendes SomeClass-Objekt damit manipulieren. Ist ja auch ein Stellvertreter für ein bestehendes Objekt.

    Und eine Klasse X soll jetzt SomeClass bzw. SomeClassProxy als Parameter erhalten, nicht wissen, was es genau ist (type erased), und einfach brav die Methoden aufrufen ohne Wissen darüber, wie die Implementierung jetzt konkret aussieht. D.h. meinetwegen kann die Klasse das auch wissen, mir doch egal, aber ich will eben möglichst elegant beides übergeben können.



  • theThing wird zur Laufzeit festgelegt, oder?



  • Genau.



  • Und wieso kannst du nicht nochmal Proxy und Nicht-Proxy die selbe Basisklasse spendieren und in SomeUIClass diese verwenden?

    Weil das Proxy nicht zwingend das ganze Interface implementieren muss, sondern manchmal auch nur einen kleinen Satz aus Funktionen von Nicht-Proxy übernimmt!?



  • Weil facepalm das als schlechten Stil angepriesen hat und mich vorläufig auch von seiner Lösung überzeugt hatte - die jetzt jedoch ziemlich unumsetzbar erscheint, wenn man SomeClass und SomeClassProxy so austauschbar nutzen möchte, wie man es eigentlich von einem Proxy gewohnt ist.

    Seine Lösung ist halt irgendwie ein CompileTimeProxy. Ich brauche einen RuntimeProxy. Aber dieses Bedürfnis kann ich mit C++ offensichtlich nicht umsetzen, ohne dass es java-style wird. Aber wenn meine Anforderung in Ordnung ist und es ohne Polymorphie nicht geht, muss eben das Interface drüber...

    Wobei ich mit dem Interface auch wieder auf Probleme stoße. Da der Stellvertreter ja nur zeigend auf das eigentliche Objekt ist, kann ich darauf schlecht einen Zeiger hingeben, so ein Proxy ist dann ja ziemlich flüchtig, wird oft Mal temporär eingesetzt, ist kopierbar... Ich könnte ihn auch nicht-kopierbar machen, aber dann stellt sich trotzdem wieder die Frage, wer für den Besitz zuständig ist. Ach ich komm gedanklich heute auf keinen grünen Zweig 😞



  • - Kann es eine Proxy-Kette geben? Also P->P->P->NP
    - Ist hinterm (letzten) P immer ein NP
    - Kann ein P mit einem anderen P ausgetauscht werden
    - Müssen die P's dann das selbe NP haben
    - ...



  • - Kette: Theoretisch ja, praktisch zurzeit aber nicht notwendig
    - Kette endet definitiv mit NP, genau
    - da es nur ein P gibt, kann natürlich ein P mit einem anderen ausgetauscht werden; bzw. wenn es später noch einen weiteren P gibt, dann soll auch das gehen
    - P dasselbe NP haben verstehe ich nicht ganz, ein Proxy hat ja entweder einen Verweis auf einen anderen Proxy oder auf einen Nicht-Proxy, somit ist klar, dass eine Kette immer auch nur einen NP haben kann



  • Kannst du mir zeigen, wie das hier nicht geht?

    class Logger { // oder was das auch machen soll
      virtual void logDoThis() {} // sehr wahrscheinlich braucht es nur
      virtual void logThat()   {} // eine einzige Methode. Dann nimm aber
                                  // lieber std::function
    };
    class RealLogger {
      virtual void logDoThis() { ... }
      virtual void logThat()   { ... }
    };
    
    struct LoggedSomeClass {
      unique_ptr<SomeClass> s;
      unique_ptr<Logger> l;
    
      void doThis() { l->logThis(); s.doThis(); }
      void doThis() { l->logThat(); s.doThat(); }
    };
    
    class SomeUIClass
    {
    private:
         unique_ptr<LoggedSomeClass> someClassOrProxy;
    public:
         void SetSomeClassOrProxy(unique_ptr<SomeClass> s)
         { someClassOrProxy.reset(new LoggedSomeClass{move(s), new Logger()}); }
         void SetSomeClassOrProxy(unique_ptr<LoggedSomeClass> s)
         { someClassOrProxy = move(s); }
    
         void doSomething()
         {
              // ...
              someClassOrProxy->doThis();
              // ...
         }
    };
    

    Proxyketten lassen sich durch eine Klasse KettenLogger abbilden.



  • Ah, ich habe

    class RealLogger : public Logger {
    

    vergessen, daher das Missverständnis.



  • Ah okay, das klärt zumindest.

    Na ja und sonst ist das mit dem Logger halt eine Vereinfachung von mir gewesen, die nicht so zutrifft.

    class SomeClass
    {
    public:
        void meth1(int a);
        bool meth2();
        void meth3(int a, int b);
    };
    
    class SomeClassTreeProxy
    {
    public:
        SomeClassProxy(SomeClass& someClass, Tree& tree) : /* ... */
    
        void meth1(int a)
        {
             // hier wird a vor Weitergabe z.B. geändert
             a += 10; 
             someClass.meth1(a);
        }
    
        bool meth2()
        {
             bool result = someClass.meth2();
             // hier wird das Resultat nochmal anhand der Nachbarn/Eltern im Baum geändert
             return result & /* irgendwelche Abfragen */;
         }
    
         void meth3(int a, int b)
         {
              // hier geschieht wiederum etwas vom Baum Abhängiges
              a -= 5; b *= 2; // beispielhaft natürlich
              someClass.meth3(a, b);
         }
    
         void meth4()
         {
              // das ist sogar eine Zusatzmethode; wer den Proxy nutzt,
              // hat das noch zusätzlich, weil SomeClass im Baum-Kontext
              // steht
         }
    };
    

    Das ist jetzt wieder abstrahiert und ich hoffe, es wird einfach klar, dass der Proxy nicht einfach NUR filtert oder NUR loggt oder NUR dies und das macht, sondern als Proxy für eine SomeClass dient, die sich im Baumkontext befindet.

    Im Baum selbst ist SomeClass abgelegt, wenn man vom Baum aber SomeClass erhalten möchte, erhält man den SomeClassTreeProxy, damit der Proxy brav berücksichtigt, dass SomeClass in einem Baum eben anders agiert.

    Erscheint vielleicht erstmal nicht intuitiv, funktioniert aber (mit Polymorphie zumindest) sauber: Wenn ich über den Tree an eine Range gehe und das Resultat jemandem übergebe, wird sichergestellt, dass alle Einfügeoperationen unter Berücksichtigung des Baumes erfolgen.

    Dadurch sollte jetzt auch verständlich werden, wieso ich nicht einfach einen std::vectorstd::function einbauen kann, der Mal brav mit einer manipulate(...)-Methode aufgerufen wird. Und SomeClass soll eben auch außerhalb des Baumes verwendbar sein - dann aber ohne Filter und Abwandlungen und Extraprüfungen usw. Würde ich jetzt jede Art von Zusatz rausnehmen und extra einbauen wie beispielsweise extra std::function für Filter, extra std::function für Prüfungen des Rückgabewertes usw., dann hätte ich ziemlich viel Gedöns drin - was auch einfach kein Mensch braucht, der nur SomeClass nutzt. Daher der ganze Proxy.

    Hilft das? Sorry für den ganzen Text...

    Wenn's hilft, kann ich noch konkreter werden.



  • Na ja, wenn jetzt einfach nichts mehr kommt, löse ich es halt über das Interface. Interessiert mich nicht, ob irgendjemand dazu kommentiert, dass es "javalike" wäre. Dynamische Polymorphie löst man eben nicht über Templates. Und manchmal braucht man dynamische Polymorphie eben - auch in C++.

    Jedenfalls komme ich nicht mit Lösungen weiter, die sich darauf fokussieren Interfaces und Polymorphie abzuschaffen statt mein Problem mit allen Anforderungen zu lösen...

    Edit:
    Den Rest habe ich Mal wegeditiert, hat leider den falschen Ton angenommen, Entschuldigung dafür. Danke an hustbaers nachfolgende Antwort. 👍



  • Eisflamme schrieb:

    Na ja, wenn jetzt einfach nichts mehr kommt, löse ich es halt über das Interface. Interessiert mich nicht, ob irgendein Praxisferner dazu kommentiert, dass es "javalike" wäre, es gibt einfach keine "c++-like" Implementierung, die meine (validen und total natürlichen) Anforderungen akzeptiert.

    C++ ist eine multi-paradigm Sprache, und Runtime-Polymorphie über Basisklassen mit virtuellen Funktionen ist "perfectly C++-like".

    Ein pöser Javaismus wäre alle solchen Klassen auf Krampf mit I zu prefixen und *nur* aus virtual-pure Funktionen bestehen zu lassen. Wenn eine Funktion nie überschrieben werden muss oder sogar darf, dann soll die auch nicht virtual sein. Ein weitere pöser Javaismus wäre einfach überall mit polymorphen Basisklassen draufzuhauen, auch wenn es in C++ viel elegantere Möglichkeiten dafür gibt. Nur so einen Fall hast du ja nicht. Es gibt keine elegantere Möglichkeit. Zumindest keine mir bekannte.



  • Okay, dankeschön. Wenn ich dasselbe schreibe, wird's ja geflissentlich ignoriert. Gut das Mal bestätigt zu hören. 🙂



  • ob irgendein Praxisferner dazu kommentiert

    Ich programiere seit etwa 20 Jahren. Davon viele mit C++. Ich verdiene mein Geld mit C++.

    Und jetzt du: Wie sehen deine Praxiserfahrungen mit C++ aus? Nein, nicht Java, PHP oder ...

    Fuer mich wirkst du unerfahren, engstirnig und beratungsresistent.

    die beendet jetzt vielleicht Mal diese Idiotie hier.

    Du schreibst nicht mehr?

    PS: Selten so einen kindischen Post gelesen, alle als Idioten zu bezeichnen, die Fassung verlieren und dann noch Hilfe erwarten.



  • hustbaer schrieb:

    Ein pöser Javaismus wäre alle solchen Klassen auf Krampf mit I zu prefixen...

    Javaismus ist nur, wenn das Interface auf -able endet. Sorry für Offtopic 🙄



  • knivil:
    Erstmal sorry, ich wollte Dir nicht auf den Fuß treten. Mein Beitrag war in der Tat nicht gelungen. Du fühlst Dich wegen dem "javalike" angesprochen? Ich meinte damit nicht Dich, ich meinte eigentlich nur abstrakt gesprochen diejenigen, die mir Lösungen angeboten haben, die nicht zu meinem Problem passten, Hauptsache aber auf Interfaces verzichteten. Wenn das nach eigenem Ermessen keiner getan hat, dann braucht sich damit auch keiner angesprochen fühlen.

    Aber wenn Du so persönlich wirst, kommentiere ich zumindest Mal Deine Beiträge: Die Lösungen von Dir hatten in der Regel Lücken, haben nicht alles erfüllt, was ich wollte, und als ich das klarstellte, kam leider am Ende (erstmal schon) keine Antwort mehr. Ich habe stets versucht sie einzubauen und sie für meinen Anwendungsfall zu Ende gedacht (das ist engstirnig?). Es ging aber nicht! (traurigerweise anscheinend unwichtig) Und die Gründe dafür habe ich stets formuliert. Die Reaktion darauf war dann oft Empörung von Dir. Was soll ich da denn machen?

    Zusammengefasst: Alle Vorschläge auszuprobieren, zu durchdenken und dann zu merken, dass es nicht passt und es deswegen abzulehnen ist für Dich also beratungsresistent? Ich spare mir jetzt Mal den Spruch, der das mit den 20 Jahren Erfahrung in Relation setzt.

    Und

    PS: Selten so einen kindischen Post gelesen, alle als Idioten zu bezeichnen

    Das ist eine Unterstellung, das habe ich nicht getan (auch vor dem Edit nicht). Mit Idiotie meinte ich, dass alles, was nur nach Interface aussieht als Java-Style abgestempelt und grundsätzlich verpöhnt wird. Das heißt nicht, dass die Vertreter davon Idioten sind, nur das Konzept ist blanker Unsinn.



  • Okay, aber noch eine Frage:

    Ein pöser Javaismus wäre alle solchen Klassen auf Krampf mit I zu prefixen und *nur* aus virtual-pure Funktionen bestehen zu lassen.

    Vielleicht lese ich das falsch aber gilt der Teil nach dem "und" auch schon allein als pöser Javaismus? Denn was ist denn in dem Fall, dass eine Klasse oder Funktion ein Objekt erwartet, was eben nur durch die Schnittstelle definiert ist und wenn das eben zur Laufzeit festgelegt werden soll?

    Denn das wäre bei mir ja eben auch der Fall mit dem Proxy und dann hätte mein Interface nur pure virtual Methoden.



  • Auch wennd as nachweislich nicht ankommen wird:

    Ich habe eine Lösung ohne Interfaces gewählt, weil meine Erfahrung sagt, das "An der Stelle könnte ich A oder Wrapper<A> verwenden" keine zwingende Bedingung für late-binding ist. Ich glaube dir, das es jetzt aus deiner Perspektive und so wie du das designed hast als zwingend erforderlich erscheint, aber höchstwahrschinlich ginge es auch ohne irgendwelche Verrenkungen ohne komische Interfaces und const_cast und und und. Da du uns aber nur deine Situation und nicht dein Problem schilderst (darauf hatte ich dich ja schonmal hingewiesen) kriegst du halt nur die Pauschalaussagen.



  • Eisflamme schrieb:

    Wir können der Einfachheit aber auch sagen, dass er protokolliert, während das SomeClass nicht macht - ansonsten werden die Aufrufe weitergeleitet.

    Alter. Wegen solch einer Trivialität so ein Aufriss mit Patterngedöns und schwammigen Bezeichnungen/Buzzwords. Disorientierung pur dank OOP.

    Wie werden wir diese Pest der Softwareentwicklung nur wieder los?

    Gruß,
    euer Observer aus der AbstractBeitragFactory


Anmelden zum Antworten