Proxy-Pattern C++
-
Eisflamme schrieb:
Implementiert, mir doch egal. Oder worauf willst Du hinaus? Und wieso ist C++ jetzt plötzlich eine funktionale Programmiersprache?
Liess bitte nochmal, ich habe nicht gesagt, dass C++ functional ist, aber das ist der beste Weg genanntes Problem anzugehen und ist einfach in C++ umzusetzen.
Zustand des Ausgangsobjekts verändern sollen
Warum? Dann mach halt sowas wie Zahlenmenge.apply(Filter) odder set/add_input_filter(Filter) bzw. output ... . Ich sehe immer noch nicht, warum da soviel vererbt werden soll. Na gut, wenn du nicht alles in im Container machen moechtes, kannst auch ein Proxy nehmen, der die Methoden mit Filterung realisiert, aber ich wuerde es anders machen. Wenn nur Primzahlen eingefuegt werden sollen, wuerde ich erst die Zahl testen und dann einfuegen, anstatt die Zahl einfuegen und hoffen, dass sie auch in der Menge landet.
Oder worauf willst Du hinaus?
Das hier ist nicht das Java-Forum.

-
Steht eigentlich alles im OP:
Ein Anwendungsbeispiel ist, dass ich einen Baum habe, sich darin Mengen befinden und die Intuition vom Tree ist, dass ParentNode für Obermenge und ChildNode für Untermenge besteht, die Mengen auf einer Ebene aber zueinander exklusiv sind.
Wenn ich jetzt bei einer Menge auf ChildNode-Ebene etwas einfügen möchte, will ich einfach meine Insert-Methode aufrufen wie vorher auch. Aber wenn ich da etwas einfügen will, was nicht in der Parent-Menge ist, soll die Einfügeoperation eben nichts einfügen (man kann auch sagen, ich will eine Range von 1-20 einfügen).
Das ist ein Beispiel. Alle Methoden können sowohl ihre Parameter filtern lassen als auch den Rückgabewert.
zahlenmenge.apply(filter);bzw.zahlenmenge.addFilter(filter);wären auch Möglichkeiten, stimmt. Das läuft aber im Endeffekt aufs selbe hinaus wie mein aktueller Ansatz, nur dass mein Ansatz den Vorteil hat, dass die eigentliche Mengenklasse keine Filter kennen muss. Zudem muss ich bei diesem Filter, dann in jeder Methode die entsprechenden Filtermethoden aufrufen. Und für den Filter benötige ich ebenfalls ein Interface... wo liegt da der Vorteil? In erster Linie sehe ich da mehr Schreibaufwand und ich muss meine Mengenklasse mit der Filterthematik belasten. Und da das jede Methode potenziell betreffen kann, muss der Filter eh wieder dasselbe Interface wie meine Mengenklasse haben.Edit:
Moment, wenn apply(filter) jetzt keinen Verweis auf einen Filter bedeutet, sondern der einmalig angewendet wird, gefällt mir der Vorschlag noch weniger. Dann wird erst alles eingefügt und nachher wird es rausgeworfen. Dann filtere ich es doch viel lieber sofort. Ich finde es auch viel komfortabler, wenn ich die Klasse nutzen kann und nicht jedes Mal nach irgendeinem Methodenaufruf explizit den Filteraufruf machen muss. Dann vergesse ich den einmal und habe inkonsistente Daten, finde ich nicht gut.Noch ein Edit bzgl. Deinem x-ten Edit bzw. bzgl. Deinem Vorher-Test:
Wo erfolgt der denn? Nicht jede Menge befindet sich im Tree.Da erstellte ich doch lieber direkt ein Interface, lasse Filter davon erben und die reichen es eben weiter. Proxy eben. Kann man auch gut in C++ einsetzen, ich sehe nicht, wieso, richtige Argumente habe ich noch nicht gehört. Vielleicht hätte ich Java auch gar nicht erst erwähnen sollen...
Und bzgl. dem ganzen Geerbe:
Ich schätze, das wird ohnehin rausoptimiert. Ich verliere vielleicht etwas Performance durch Aufrufe und Indirektionen, ich gewinne aber erhöhte Abstraktion. Ich kann an irgendjemanden irgendeinen Filter weiterreichen, in Form der Interface-Klasse, und der muss sich keine Gedanken um Filter und anderes Zeug machen. Der benutzt die Klasse einfach gemäß des Interfaces und kann sich stets gewiss sein, dass der Inhalt gemäß der wo-auch-immer festgelegten Kontexte konsistent ist. Da ich leider häufig mit Inkonsistenzen zu kämpfen hatte, weil ich irgendwo "nicht aufgepasst habe", gewinne ich dadurch viel Stabilität.
-
Und für den Filter benötige ich ebenfalls ein Interface... wo liegt da der Vorteil?
1.) Typisch Java.
2.) Gibts es 2 getrennte Entitaeten, die lose gekoppelt sind.
3.) Das Interface ist trivial f: T -> bool, kann sehr leicht durch std::function abgebildet werden.nur dass mein Ansatz den Vorteil hat, dass die eigentliche Mengenklasse keine Filter kennen muss
Dann baue dir einen einfachen Adapter.
Zudem muss ich bei diesem Filter, dann in jeder Methode die entsprechenden Filtermethoden aufrufen.
Wie meinst du das? Natuerlich muss, geprueft werden ob die Zahl eingefuegt werden darf.
apply(filter) jetzt keinen Verweis
dann nimm set.
Java auch gar nicht erst erwähnen sollen
Habe ich nichtmal gelesen, war irgendwie klar.
Ich schätze, das wird ohnehin rausoptimiert. Ich verliere vielleicht etwas Performance
Darum geht es nicht, ob irgendwas herausoptimiert wird. Zu guter Sofwarequalitaet gehoert lose Kopplung und leicht wartbar.
Ich kann an irgendjemanden irgendeinen Filter weiterreichen, in Form der Interface-Klasse, und der muss sich keine Gedanken um Filter und anderes Zeug machen
Und ich wuerde einfach std::function<bool (T)> oder so weiterreichen.
PS: Ich habe den Eindruck, dass du deine Loesung bereits gefunden hast und eigentlich keinen Rat suchst. Tut mir leid, dass ich es einfach ganz anders machen wuerden und deinen Vorschlag nicht toll finde, da zu wenig die Standardbibliothek genutzt wird und die Kopplung ueber is-a-Beziehungen zu stark sind.
-
1 ist für mich kein Argument, sorry.
2: Stimmt, in meinem Fall kennt der Filter die zu filternde Klasse. Die Abhängigkeit könnte man in der Tat noch rausbekommen. Preis ist aber, dass meine Mengenklasse einen vector auf Filter braucht, darüber bei jedem Funktionsaufruf iterieren muss und dann Methoden aufruft. Ich versaue mir also ziemlich meinen Mengenklassen-Code.
3: Leider geht es nicht nur ums Einfügen und Auslesen. Es gibt nicht-triviale Operationen, die ebenfalls anders aussehen können. Potenziell jede Funktion kann gefiltert werden und das auf andere Art und Weise. Das passt nicht mehr ganz zur Mengenklasse-Analogie, verdeutlicht aber vielleicht auch meine Antwort zu 2, die man so sonst vermutlich nicht nachvollziehen kann. Soweit ich Deinen Vorschlag verstehe, fällt damit function<bool(T)> aber auch raus, richtig? Der Filter ist ja nicht nur elementweise, sondern kann auch andere Funktionen betreffen.
Das geht leider wieder etwas von der Mengenanalogie weg. Die darin enthaltenen Objekte sind halt wiederum bestehend aus Unterelementen. Und der Filter kann eben ein paar von den Unterelementen reinlassen, andere wiederum nicht. Ich will gar nicht so sehr auf die Details eingehen, man kann das einfach gut dazu abstrahieren, dass jede Methode anders gefiltert werden kann und die Frage nicht einfach grundsätzlich pro Element ja/nein ist. Edit: Selbst der Name "Filter" ist nicht ganz sauber, trifft das, was die Klasse machen soll, aber am Besten. Nicht ganz sauber, weil manche Methoden auch einfach abgewandelte Resultate liefern.
Adapter: Wenn Mengenklasse <-> Adapter <-> Filter, dann kann ich ja direkt Mengenklasse <-> FilterProxy nutzen. Kopplung nur noch seitens Filter auf Mengenklasse (was ok ist, weil der Filter spezifisch ist und eh für keine anderen Mengenklassen benutzt werden kann). Ich sehe, dass es noch stärker entkoppelt. Aber es ist bei mir wirklich kein Vorteil, wenn der Filter den Filtee nicht zu kennen braucht. Und den Nachteil von Vererbung sehe ich auch nicht, das ist dann ja auch keine andere Indirektion als über eine Adapterklasse. (ja, Komposition/Aggregation ist entkoppelter als Vererbung, aber wir sprechen hier nur davon, dass sich die Objekte eine gemeinsame Schnittstelle teilen, das koppelt überhaupt nicht)
Set: Das hätte man direkt auf "Mengenklasse" sagen können, Set ist eine. Aber das ist wie gesagt ein vereinfachtes Beispiel und ich habe im OP darum gebeten einfach zu akzeptieren, dass ich hier eine Klasse für sich benötige.
Darum geht es nicht, ob irgendwas herausoptimiert wird. Zu guter Sofwarequalitaet gehoert lose Kopplung und leicht wartbar.
Ja. Lose Kopplung ist für gute Wartbarkeit und Erweiterbarkeit aber eben nur ein Grund. Es gibt andere und damit versuche ich hier auch permanent zu argumentieren.
PS: Ich habe den Eindruck, dass du deine Loesung bereits gefunden hast und eigentlich keinen Rat suchst.
Das, was ich habe, ist eine Lösung. Wenn es bessere gibt, will ich davon hören. Aber ich sehe in Deinen Vorschlägen keine Vorteile - was ich aber auch begründe. Ich akzeptiere Deine Antworten nicht und übernehme sie blind, wenn ich sie nicht gut finde. Erwartest Du, dass ich es trotzdem tue oder was soll diese unnötige Aussage? Wenn Du meine Argumente nicht schlüssig findest, ist das was anderes, aber unterstelle mir bitte nicht, dass ich einfach blind und ohne nachzudenken alles ablehne, was Du schreibst, denn dem es mitnichten so.
-
Typisch Java
Das bedeutet, dass in C++ anders programmiert wird. Loesungen fuer Probleme unterscheiden sich manchmal gravierend. Und wenn ein Design nach Java riecht, dann ist der Entwurf fuer die Sprache C++ eher schlecht. Das ist nur eine Daumenregel. Gruende und Argumentation kannst du gern im Netz nachlesen, ich will sie nicht wiederholen.
Es gibt nicht-triviale Operationen, die ebenfalls anders aussehen können.
Dann sind es eben keine Filter sondern Modifizier. Konzeptionell aehnlich.
Es gibt andere und damit versuche ich hier auch permanent zu argumentieren.
Du kennst dein Problem ganz genau, weil du es implementiert hast. Ich kenne nur deine Beschreibung der Loesung und die stinkt nach Java.
ich irgendwo "nicht aufgepasst habe" häufig mit Inkonsistenzen zu kämpfen hatte
Auch scheinst du bei deinem Design eigene Unzulaenglichkeiten auszubuegeln, die erstmal nicht mit dem Problem was zu tun haben. Ich kaempfe mit diesen Problemen nicht, auch wuerde ich sie ausserhalb angehen, z.B. innerhalb von Tests.
wieder etwas von der Mengenanalogie weg ... jede Methode anders gefiltert ... nicht einfach grundsätzlich pro Element ja/nein ... Nicht ganz sauber
Du hast es selbst erkannt, dein Konzept ist unsauber.
-
Auch scheinst du bei deinem Design eigene Unzulaenglichkeiten auszubuegeln, die erstmal nicht mit dem Problem was zu tun haben.
Doch, weil die Probleme daraus resultieren, dass die Benutzung meiner gefilterten Klasse ziemlich unintuitiv war. Mache ich das intuitiv und so, wie man es sich als Klassenbenutzer wünscht, fallen sie automatisch weg.
Du hast es selbst erkannt, dein Konzept ist unsauber.
Es ist nicht gemäß der Terminologie einer Menge sauber. Das Konzept per se ist dadurch nicht betroffen, jedenfalls sehe ich das nicht und Du meintest eben selbst, dass Du die Details davon nicht hast. Es ist halt keine Menge, sondern so ähnlich. Klar wäre es schöner, wenn es einfach eine ist, aber na ja.

Ich zerbreche mir gerade die ganze Zeit den Kopf. Der von Dir vorgeschlagene Filter hat echt Vorteile, jetzt kommt aber dazu, dass bei der Änderung einer Obermenge die Untermengen elementweise verifiziert werden müssen. Somit führt eine Obermengenänderung zu einer Operation. Das kann man mit dem Proxy sauber lösen, da führt Löschen oder Neusetzen der Range einfach dazu, dass die Untermengen revalidiert werden.
Macht man es über Filter/Modifier, müsste der getriggert werden. Rangeänderung ist als Event (Signal) eigentlich zu viel, weil beim Hinzufügen aus der Untermenge ja nichts gelöscht werden muss. Sieht man davon aber ab, so könnte man Rangeänderung mit Filteranwendung auf jedes Kind nutzen. Den Signal/Slot-Mechanismus könnte man beim Hinzufügen/Löschen im Baum ja hinzufügen/löschen. Würdest Du das so lösen?
Edit: Also wenn ich da wieder mehr drüber nachdenke, finde ich das nicht wirklich schön. Dass die Unterbäume revalidiert werden müssen, wenn sich eine Obermenge ändert, ist ja nur ein Fall. Da muss ich dann ständig für alle möglichen Fälle Signale hin- und herschießen. Die Proxy-Lösung leitet einfach per se erstmal alle Aufrufe durch den Proxy weiter. Das ist halt auf Methodenebene ein Filter und ist prima erweiterbar, wenn das Verhalten einer anderen Methode sich ändert, wenn es eben in einem Baum vorkommt. Statt nach einem Methodenaufruf einen Filter anzuwenden oder ihn der Klasse zu geben, habe ich einfach direkt ein Objekt/einen Wrapper (wenn man so will, das Pattern ist es nicht) um meine eigentliche Klasse und muss mir keine Gedanken mehr machen. Ist doch total benutzerfreundlich.
Ich will da nicht zwanghaft an meiner Lösung festhalten, aber das erscheint mir relativ hilfreich. Und ich kann die Filter einfach super hintereinanderschalten, da ich einem Filter sagen kann, er soll auf einem anderen basieren. Kann auch gut sein, dass mir beim Implementieren noch auffällt, dass irgendeine andere Methode auch noch etwas machen soll, bevor sie den Aufruf weiterleitet, oder den Aufruf anders gestaltet. Ich sehe nicht, wo das was mit Java zu tun hat, sind Proxys allgemein für dich Java? Auch wenn man Dinge protokollieren möchte oder Remote-Objekte nutzt? (andere Standardfälle für Proxys) Aber vielleicht fällt Dir zu der Baum-Update-Variante ja auch etwas ein. Klar ist, dass ich einen Filter einbauen kann, noch Signale/Slots einbauen kann für diverse Benachrichtigungen und auch brav ein Flag übergeben kann, falls es sich bei der Range um eine handelt, die im Tree ist.
Oder ich mache einen Proxy, der alles automatisch abhandelt und der natürlich auch nur für den jeweiligen Spezialfall eingesetzt wird. Proxy zu Kontext ist 1 : 1, das ist für mich gerade intuitiv und sauber. Je nach Kontext nutze ich eben einen anderen Proxy, der automatisch alles so macht, dass es meinen Erwartungen entspricht.
-
Hat vielleicht sonst noch jemand Anmerkungen? Kann ja direkt aufs OP bezogen sein, die Diskussion ist vermutlich nicht unbedingt nachvollziehbar...
-
Eisflamme schrieb:
Hat vielleicht sonst noch jemand Anmerkungen?
Schwer zu sagen... Auch dein OP ist nicht ganz leicht nachvollziehbar. Dir ist genau klar (hoffentlich), was du hast und was du machen willst, wir haben erstmal nur eine recht vage Beschreibung. Vielleicht ist die auch gar nicht so vage, es fällt mir generell schwer, irgendwelche Vorschläge zu geben oder etwas zu bewerten, ohne das Problem genau zu verstehen.
Die Beispiele im ursprünglichen GoF Buch waren übrigens in C++ und da kommt auch das Proxy Pattern vor.
Ich würde sagen, es kommt stark drauf an, wie deine Klassen benutzt werden. Dein Ansatz erinnert tatsächlich an Java, aber mich würde es nicht so stören. Ich finds oft praktisch, gegen Schnittstellen zu programmieren, und wenn man irgendwo im Code ein Objekt bekommt, von dem man nur die Schnittstelle kennt und es benutzt, ohne irgendwas zu konfigurieren, ist es vielleuicht einfacher/eleganter, mit Proxies zu arbeiten. Aber nur vielleicht. Wahrscheinlich könnte man fast immer auch mit dem Vorschlag von knivil gut arbeiten, das ist dann irgendwo Geschmackssache. Für sowas wie Filter würde ich es eher wie knivil angehen. Aber wenn du ein Objekt mit mehreren Methoden hast, und evtl. das Verhalten komplett ändern können willst, würde ich eher einen Proxy bauen.
Worauf du mit dem Template hinaus willst, hab ich nicht ganz verstanden. Ich würde hier wahrscheinlich kein Template nehmen, außer natürlich, ich hab dich komplett falsch verstanden. Ein Proxy leitet auch von der gemeinsamen Schnittstelle ab, somit würde ich dem Proxy das drunterliegende Objekt als Zeiger auf die Schnittstelle übergeben, damit kann man die auch problemlos verschachteln.
-
Hi Mechanics,
vielen Dank! Ich bin mir bewusst, dass mein OP nicht sehr konkret ist und daher nicht so einfach nachvollziehbar. Ich will auch wirklich keine Kristallkugel-Beiträge erstellen, nur versuche ich meine Probleme einfach immer runterzubrechnen, aber so Strukturfragen sind runtergebrochen (bei mir) halt trotzdem noch schwammig.
Dennoch hilft mir Deine Antwort sehr weiter!
Für sowas wie Filter würde ich es eher wie knivil angehen. Aber wenn du ein Objekt mit mehreren Methoden hast, und evtl. das Verhalten komplett ändern können willst, würde ich eher einen Proxy bauen.
Das ist halt der Punkt. Manche Methoden stoßen noch Signale an, die aber nicht angestoßen werden, wenn man das ursprüngliche Objekt nutzt. Andere filtern. Und wieder andere haben sogar Zugriff auf dritte Objekte, weil Änderungen an einer Stelle im Baum auch andere Stellen betreffen.
Insofern ist ein Filter nicht der einzige Teil (auch wenn ich das ursprünglich so nannte). Man könnte vermutlich Filter mit zwei Signalen, die im normalen Objekt schon gefeuert werden (obwohl da unnötig), kombinieren. Ich bin aber auch unsicher, ob es nicht noch weitere Erweiterungen zukünftig gibt, die wären im Proxy eben auch gut aufgehoben. Der Proxy ist eben das Vermittler-Objekt für meine eigentliche Klasse unter dem Kontext, dass selbige in einem (bestimmten) Baum zugegen ist.
Ein Proxy leitet auch von der gemeinsamen Schnittstelle ab, somit würde ich dem Proxy das drunterliegende Objekt als Zeiger auf die Schnittstelle übergeben, damit kann man die auch problemlos verschachteln.
Die Sache ist, ich las in einem Artikel dazu, dass Proxys (im Gegensatz zu Decoratorn) das zu verweisende Objekte selbst erstellen können, die Verankerung kann hier also fest sein, was mir auch gut passt, da ich als Benutzer der Klasse nicht jedes Mal die ganze Verschachtelungsebene mitgeben lassen möchte. Und da die Verankerung zur Kompilierzeit gegeben ist, wird eben in der Basisklasse das instanziiert, was als Templateparameter angegeben wird.
Denkst Du, das ist Käse? Einfach Factory drüber, damit diese Verschachtelungen rausgekriegt werden? Finde es halt in meinem Fall in Ordnung, wenn Lebenszeit von Proxy und dazugehörigem Objekt übereinstimmen.
Besten Gruß und danke!
Eisflamme
-
Eisflamme schrieb:
Die Sache ist, ich las in einem Artikel dazu, dass Proxys (im Gegensatz zu Decoratorn) das zu verweisende Objekte selbst erstellen können, die Verankerung kann hier also fest sein, was mir auch gut passt, da ich als Benutzer der Klasse nicht jedes Mal die ganze Verschachtelungsebene mitgeben lassen möchte.
Mir war jetzt nicht klar, dass du so viel Wert auf den Unterschied zwischen Proxy und Decorator legst. Die Unterscheidung finde ich persönlich völlig unwichtig, auch wie man das bezeichnet. "Proxy" ist halt ein Konzept und ein geläufigerer Name als Decorator. Für mich ist vor allem wichtig, dass der Proxy oder Decorator ein anderes Objekt mit derselben Schnittstelle wrappt.
Ich würde mich da nicht so sehr an diesen Kleinigkeiten aufhängen. Nimm das, was dir besser passt. Wenn dein Proxy das drunterliegende Objekt selbst instanziieren soll, kannst du schon so ein Template bauen. Spricht hier was gegen CRTP?
-
Habe mich erst jetzt mit CRTP beschäftigen können. Mächtiges Teil, was ich noch nie benutzt habe. *schäm*
Aber ich möchte durchaus hin und wieder jemandem die Basisklasse geben können, ohne dass er weiß, was es damit auf sich hat. Beispielsweise kann es auch vorkommen, dass jemand ein NumberSet erhält, darauf beliebig herumspielen kann, aber nicht weiß, dass es sich eigentlich innerhalb eines Baumes befindet. Das macht dann aber nichts, weil der Proxy automatisch regelt, dass keine Inkonsistenzen entstehen.
Nur soll das eben offen bleiben. Damit ist CRTP wohl nicht die Wahl. Ich bin mit meiner aktuellen Umsetzung übrigens noch immer zufrieden, maximal flexibel, was ich auch alles zukünftig gebrauchen kann für diverse Szenarien.

