Akzeptanz unterschiedlicher Proxys für Klasse



  • 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



  • Ich hab bisher den Thread nicht wirklich verfolgt, ich hab quasi nur mitbekommen, dass du ein Objekt O hast, welches ein bestimmtes Interface I hat und du willst einen Proxy, der dein Objekt O hält, das Interface I erfüllt, aber noch ein paar Funktionalitäten (Logging) hinzufügt.

    Wäre da nicht das Decorator Pattern was?

    Sorry, wenn ich mit obigen Annahme falsch lag und das absolut nicht passt, dann ignoriert mich bitte.



  • Skym0sh0:
    Decorator und Proxy unterscheiden sich ja nicht besonders voneinander. Proxy ist mit dem eigentlichen Objekt einfach härter verdrahtet. Aber ich finde die Unterscheidung nicht so wichtig, im Grunde genommen wäre es dasselbe. 🙂

    otze:
    Immer, wenn ich anreiße, dass der Proxy als Stellvertreter für ein im Baum befindliches Objekt steht (was imo sehr konkret ist) und auch schildere, dass es sich um einen Baum von Mengen handelt, die in bestimmten Verhältnis zueinander stehen, kriege ich keine Antworten mehr. Ich habe schon häufig versucht das genaue Problem irgendwann später im Thread zu schildern, aber dann wird es zu lang und keiner liest mehr. 🙂

    Wie auch immer, danke für den Einwand. Ich muss eine ganze Menge umstrukturieren, wenn ich die Anforderung an das dynamische Setzen auflöse. Allerdings komme ich mit dem Interface auch nicht wirklich auf einen grünen Zweig.

    Angenommen, ich nutze es statisch, ergibt sich dann aber immer noch das Problem, dass Proxy und eigentliches Objekt sich darin unterscheiden, dass ersterer kopierbar ist, weil er ja als Zeiger dient. Wie bringe ich das auf eine Ebene? Im Baum selbst befindlich sind ja die eigentlichen Objekte, nur eben der Zugriff auf diese geschieht über den Proxy. Wie würdest Du denn Proxy und eigentliche Klasse auf eine Ebene bringen, sodass die als Templateargument übergeben werden können und beide funktionieren?

    Hm, man könnte natürlich für den Proxy operator-> überladen, dann könnte man einerseits EigentlicheKlasse* und Proxy übergeben und der Proxy würde sich verhalten wie ein Zeiger. Wäre das nicht was? Hmmm, das gefällt mir eigentlich echt gut.

    Also etwa so:

    class SomeClass
    {
    // ...
    };
    
    class SomeClassProxy
    {
    public:
        SomeClassProxy(SomeClass& someClass) : someClass(&someClass) {}
    
        SomeClassProxy& operator->() {return *this;}
    
        void foo() {someClass->foo();}
    
    private:
        SomeClass* someClass;
    };
    
    template<typename SomeClassReference>
    class SomeUIClass
    {
    private:
        SomeClassReference someClass;
    
    public:
        SomeUIClass(SomeClassReference someClass) : someClass(someClass) {}
    };
    
    int main()
    {
        SomeClass a;
        SomeClassProxy b(a);
    
        typedef SomeUIClass<SomeClass*> Ui1;
        typedef SomeUIClass<SomeClassProxy> Ui2;
    
        Ui1 ui1(&a), ui2(b);
    }
    

    Das entspricht auch brav meiner Logik, dass der Proxy eigentlich ja nur ein erweiterter Zeiger ist. Etwas blöd ist, dass man mit . genau so gut wie mit -> auf den Proxy zugreifen kann. Sollte man das über ne innere Klasse oder so noch absichern? Dann könnte man auch weitergehen und:

    template<bool isConst>
    class SomeClassProxyBase
    {
        struct ProxyImpl
        {
            SomeClass* someClass; // oder const je nach isConst mit enable_if etc.
            ProxyImpl(SomeClass& someClass) : someClass(&someClass) {} // ibid.
    
            void foo() {someClass->foo();}
            void bar() const {someClass->bar();}
    
        } proxyImpl; // und wenn isConst true ist, kann man proxyImpl jetzt const machen
    
    public:
        SomeClassProxyBase(SomeClass& someClass) : proxyImpl(someClass) {} // oder const je isConst
    
        void SetSomeClass(SomeClass& someClass) {proxyImpl.someClass = &someClass;} // ibid.
    
        ProxyImpl& operator->() {return proxyImpl;} // oder wieder const
    };
    
    // jetzt geht:
    typedef SomeClassProxyBase<true> ConstSomeClassProxy;
    typedef SomeClassProxyBase<false> SomeClassProxy;
    
    // und auch die vier Varianten
    const ConstSomeClassProxy proxy1; // Bezugsobjekt nicht austauschbar, nicht änderbar
    ConstSomeClassProxy proxy1; // Bezugsobjekt austauschbar, nicht änderbar
    const SomeClassProxy proxy1; // Bezugsobjekt nicht austauschbar, doch änderbar
    SomeClassProxy proxy1; // Bezugsobjekt austauschbar, und änderbar
    

    Das fände ich chic, nein?



  • kannst du mich auf den Beitrag verlinken, an dem du dein Problem beschrieben hast?



  • Ja, sehr gerne. Also kurz angerissen z.B. hier:
    http://www.c-plusplus.net/forum/p2347795#2347795

    Näher erläutert wird der Baum aber v.a. hier, auch wenn es dort um ein etwas anderes Thema geht:
    http://www.c-plusplus.net/forum/p2347769#2347769

    Und eine dritte Ausführung war in einem früheren Thread, aber der zweite hier zeigt es eigentlich ganz gut, denke ich.

    Der TreeProxy bezieht sich nur auf MengenNodes, die FolderNodes sind ja in der Hinsicht uninteressant. Vielleicht ist der Proxy auch nicht das Mittel der Wahl, aber einen Filter über setFilter() in SomeClass einzuführen, ist halt nicht genug. Beispielsweise führt das ändern einer Menge im Baum dazu, dass die Mengen der Kindknoten revalidiert werden müssen. Da müsste ich in der Mengenklasse von Vornerein schon so etwas vorsehen über std::function oder so. Ich will aber nicht die Schnittstelle der Menge verschändeln, weil man sie in einem Baum benutzen kann, das ist einfach nicht deren Verantwortung. Daher finde ich so eine Proxyklasse schon recht elegant.

    [offtopic]
    Hm, aber ich verstehe jetzt erst so langsam, wieso man Templates dynamischer Polymorphie vorziehen sollte, also klar, es ist elganter, wird immer gesagt. Aber konkret kann man damit das const-Problem lösen (wie von knivil gesagt) und aber auch eben so Späße einbauen, dass das Objekt, das übergeben wird, mit operator-> zugreifbar wird. Bei dynamischer Polymorphie kann man nur ein Interface drübersetzen und kommt nicht weiter. Denn da könnte ich meine Proxylösung dann doch wieder nicht einsetzen.

    Macht man in Java wirklich auch das, was man zur Compiletime erledigen könnte, mit Interfaces? Was eine arme Sprache das dann ja eigentlich ist.

    Na ja, da muss ich mich jetzt nochmal entschuldigen. Ich habe das vorher mit den Vorteilen der Templates noch nicht so klar gesehen und ich hab auch noch viel zu lernen, was die Klarheit angeht. 🙄 Ist aber auch echt nicht so einfach...[/offtopic]



  • okay. damit ich das richtig verstanden habe: du hast da Nodes und je nachdem ob sie in einem Baum sind oder nicht, verhalten sie sich anders (liegt das daran, das sie Kindknoten haben und deswegen anders rechnen?)

    Und dann habe ich verstanden, dass die Nodes dann irgendwo anders(?) verwendet werden aber dort dann nicht erwünscht ist, das sie sich anders verhalten(?). Und dann ist da noch ein ominöses Problem mit der Kopierbarkeit was ich nicht ganz umrissen kriege. Ist das Problem, das sich die Knoten hinter dem Proxy weg bewegen können(dangling reference/wrong reference) oder was? Warum tritt das Problem nicht auf, wenn du keinen Proxy verwendest?

    Du siehst, so wirklich klar sind deine Erklärungen nicht 😞

    Ich glaube aber, das der Ansatz sich nur auf Nodes/Proxis zu konzentrieren alleine nicht weiter hilft, man muss auch die Seite anschauen, die das Zeug dann übergeben kriegt.



  • du hast da Nodes und je nachdem ob sie in einem Baum sind oder nicht, verhalten sie sich anders

    Ne, war falsch von mir ausgedrückt, sorry. Der MengenNode besitzt die Menge (na ja, der Baum besitzt die Menge und der Node kennt den Index, wie auch immer), er ist aber nicht dasselbe. Der Node macht eh nur innerhalb eines Baumes Sinn, die Menge will man aber auch außerhalb dessen verwenden können. Und der Proxy ist jetzt Stellvertreter für eine Menge, die sich innerhalb eines MengenNodes (und damit auch innerhalb eines Baumes) befindet.

    Kopierbarkeit möchte ich einfach, weil der Proxy ja quasi nur wie ein Zeiger funktioniert. Eigentlich erfüllt mein Code oben alles, was ich benötige und verhält sich auch genau so (so gesehen löst das mein Problem für die Nicht-Laufzeit-Variante). Der Proxy soll eben ein lightweight-Objekt sein, weil ich keine Lust habe mich um Speichermanagement für einen Proxy zu kümmern, das ergibt für mich nicht so viel Sinn.

    Und dangling references werden dadurch abgedeckt, dass ein TreeMengenProxy sich bei Initialisierung beim Baum auf ein Signal registriert, das ihm mitteilt, wenn ein Objekt verschoben wurde. Wird es gelöscht, erhält er auch Mitteilung und der Proxy wird invalid. So kann ich mir nämlich einmalig einen Proxy geben lassen, der dann solange gültig ist, wie das referenzierte Objekt sich im Baum befindet. 🙂

    Ja, mir fällt es nicht so leicht das zu erklären, tut mir Leid. Ist es jetzt klarer?



  • Ich wollte das gestern noch dazuschreiben, hab dann aber vergessen "save" zu drücken...

    Eisflamme schrieb:

    Wenn jemand mit einer Lösung kommt, die meine Anforderungen erfüllt, sehr gerne! Bisher wurde aber einfach nach der Prämisse "Hauptsache Templates, Deine Anforderungen ignoriere ich Mal geflissentlich" geantwortet... dass das in einem komplett nutzlosen Design resuliert, wurde einfach Mal ignoriert/so hingenommen.... nützt mir gar nichts, hat mich Tage Arbeitszeit gekostet für NICHTS, herzlichen Dank.

    Fass dir mal an die eigene Nase.
    Du hast z.B. nicht dazugeschrieben dass du die Möglichkeit brauchst das "geproxyte" Objekt zur Laufzeit gegen ein anderes mit anderem Typ auszutauschen.

    "Die" Standardlösungen für das "const Problem" wolltest du nicht haben:

    • ConstProxy tut in den non-const Funktionen einfach nix
    • ConstProxy wirft in den non-const Funktionen eine Exception

    Und dass das du dir mit dieser "const" Sache ein Problem eingeredet hast, das gar nicht existiert -- speziell bei einem Logger nicht -- da können wir auch nix dafür.

    Dieser Thread ist übrigens ein gutes Beispiel dafür worauf ich im "Signals" Thread hinaus wollte: es gibt keine Silver-Bullet, und wenn man sinnvoll anwendbare Antworten bekommen möchte, dann sollte man vielleicht auch die Anwendung beschreiben. Und zwar je konkreter desto besser.



  • Du hast z.B. nicht dazugeschrieben dass du die Möglichkeit brauchst das "geproxyte" Objekt zur Laufzeit gegen ein anderes mit anderem Typ auszutauschen.

    Ich habe das oft auf späteren Seiten der jeweiligen Threads formuliert. Aber Du hast Recht, ich habe die Threads durchsucht und im opening post steht das überhaupt nicht drin. Und in diesem Thread konnte ich es gar nicht finden. Auf Lösungsvorschläge habe ich es aber gesagt.

    Was die Standardlösungen angeht: Sind das echt Standardlösungen? Ich finde es aber darüber hinaus auch ohnehin unsauber, denn eigentlich müsste man von const Interface erben, was nicht funktioniert. Oder meinst Du, dass genau das eigentlich kein Problem ist?

    Was mich geärgert hat, war einfach, dass meine Lösung schlechtgeredet wurde und ich mir ständig anhören muss, wie bescheuert mein Design ist, um dann aber eine nicht fertig zu Ende gedachte Lösung zu erhalten, die angeblich so viel besser ist. Dann stehe ich da und versuche das umzusetzen, komme zu nichts und erhalte auf meine Nachfragen von entsprechenden Personen keine Antworten mehr. Das ist frustrierend. Dass die auch andres zu tun haben und ich generell überreagiert habe, steht natürlich außer Frage. Entschuldigung dafür nochmals.

    Dieser Thread ist übrigens ein gutes Beispiel dafür worauf ich im "Signals" Thread hinaus wollte: es gibt keine Silver-Bullet, und wenn man sinnvoll anwendbare Antworten bekommen möchte, dann sollte man vielleicht auch die Anwendung beschreiben. Und zwar je konkreter desto besser.

    In diesem Fall trifft das ohne jede Frage zu. Dann werden's halt lange Opening Postings, vielleicht schreckt das gar nicht so stark ab, wie ich dachte. In jedem Fall führt mein aktueller Posting-Stil bei konkreten Problem einfach zu nichts (beim signal-Thread fand ich es trotzdem zielführend :)).

    Nagut, ich sehe, ich habe hier viel falsch gemacht. Ich gelobe Besserung.

    Hat jemand Anmerkungen zu dieser Proxy-Lösung, die dann eben auch auf compiletime beruht? (ich mache einen neuen Thread dafür auf)

    template<typename T>
    class SomeClassProxyBase
    {
        struct ProxyImpl
        {
            T* someClass;
            ProxyImpl(T& someClass) : someClass(&someClass) {}
    
            void foo() {someClass->foo();}
            void bar() const {someClass->bar();}
    
        } proxyImpl; // und wenn T const ist, kann man proxyImpl jetzt const machen
    
    public:
        SomeClassProxyBase(T& someClass) : proxyImpl(someClass) {}
    
        void SetSomeClass(T& someClass) {proxyImpl.someClass = &someClass;}
    
        T& operator->() {return proxyImpl;} // wenn T const, muss natürlich noch const-Modifier hin via enable_if oder so
    };
    
    // jetzt geht:
    typedef SomeClassProxyBase<const SomeClass> ConstSomeClassProxy;
    typedef SomeClassProxyBase<SomeClass> SomeClassProxy;
    
    // und auch die vier Varianten
    const ConstSomeClassProxy proxy1(sc); // Bezugsobjekt nicht austauschbar, nicht änderbar
    ConstSomeClassProxy proxy2(sc); // Bezugsobjekt austauschbar, nicht änderbar
    const SomeClassProxy proxy3(sc); // Bezugsobjekt nicht austauschbar, doch änderbar
    SomeClassProxy proxy4(sc); // Bezugsobjekt austauschbar, und änderbar
    

    Man kann ProxyImpl eigentlich auch zum Template machen, dann braucht man darin kein enable_if-Gedöns.


Anmelden zum Antworten