Design-Problem: Composite ohne friend
-
Nexus schrieb:
Weil der Klick realistisch aussehen soll, so à la: "Schau mal, was passiert, wenn du hier klickst". Sodass der Benutzer ohne Weiteres weiss, dass ein Klick durchgeführt wurde.
Aber es fehlt mir immer noch ein: wozu?
Als wenn jemand nicht wüsste, wie ein Klick auf einen Button aussieht. Wenn schon musst du die Aufmerksamkeit zum Button lenken und diesen klar blinken oder strahlen lassen. Ich sehe keinen Sinn, wieso man diese Klickanimation überhaupt ohne Klick durch den Benutzer auslösen können muss.Nexus schrieb:
Ja, eben. Eine kleinere Schnittstelle hast du dadurch nicht, eher musst du noch Spezialfunktionen, die man sonst nicht braucht, anbieten. Soviel zum Thema "Features genau abwägen".
Du hast eine saubere und besser getrennte Schnittstelle. Wobei ich allerdings trotzdem keine solche Funktion anbieten würde. Es ging mir dabei nur um das "wenn überhaupt". Wenn zum Beispiel dann nur jemand die Animation ausführen möchte, wäre dies bei deiner Lösung unglaublich umständlich
Allerdings sehe ich, wie schon gesagt, gar keinen Grund, wieso dies jemand tun möchte.Nexus schrieb:
Ich habe es nun aber genügend oft erklärt. Auf das Hauptargument, dass du dir ohne
Click()
Workarounds basteln musst, bist du gar nie wirklich eingegangen. Wenn das nützlicher ist, okay.Und wie oft soll ich es noch sagen, dass ich gar keinen Grund für sowas sehe? Ich sehe nicht mal ein, wieso jemand einen Workaround bauen sollte. Wieso sollte er diese Schnittstelle überhaupt brauchen? Wieso? Wozu?
Nexus schrieb:
Weil ein mögliches Beispiel schlecht ist, braucht man die Funktionalität nicht? Sogar mit Begründung könntest du diesen Schluss nicht ziehen.
Der Satzbau war ein wenig schlecht gewählt. So extrem war das nicht gemeint. Aber bisher habt ihr meiner Meinung nach kein sinnvolles Beispiel genannt, wieso man diese Möglichkeit überhaupt brauchen würde. Das ist ja schliesslich auch mein Problem hier. Ich sehe keine sinnvolle Anwendung für die Ausführung eines Klicks über die Button-Schnittstelle.
Zusammengefasst:
Wozu? Wieso? Warum? Wofür? Wann? Wo? Wie? usw.
Ich möchte doch eigentlich nur sinnvolle Praxisbeispiele hören. Bringt ein Beispiel als Widerspruchsbeweis meiner Aussage. Um es mal mit den Mitteln der Logik zu sagen
Oder wir lassen es einfach bleiben und einigen uns darauf, dass wir uns nicht einigen können@€ schrieb:
Schau dir halt einfach andere Frameworks an.
Wer ist damit gemeint?
Wie auch immer, ist eigentlich egal wer gemeint ist, die Argumentation ist trotzdem unsinnig. Nur weil andere es anbieten und machen, heisst es nicht, dass es auch gut ist.Grüssli
-
Gut, dann möchte ich das angesprochene Beispiel verdeutlichen.
Nehmen wir an, ich habe ein Abenteuerspiel, bei dem man einzelne Gegenstände kaufen und verkaufen kann. Zudem kann man alles aufs Mal verkaufen. Es gibt die Gegenstände Schwert, Schild und Rüstung, man kann alles entweder haben oder nicht. Die Oberfläche könnte so aussehen:
Schwert Schild Rüstung +-----------+ +-----------+ +-----------+ | Kaufen | | Kaufen | | Kaufen | +-----------+ +-----------+ +-----------+ +-----------+ +-----------+ +-----------+ +---------------+ | Verkaufen | | Verkaufen | | Verkaufen | |Alles verkaufen| +-----------+ +-----------+ +-----------+ +---------------+
Nun habe ich ein Schwert und möchte es loswerden. Mit dem Verkauf kann ich mehrere Aktionen verknüpfen:
Button BuySword("Kaufen"); Button SellSword("Verkaufen"); // Da man kein Schwert mehr verkaufen kann, Button deaktivieren SellSword.AddClickListener(tr1::bind(&Button::SetActive, &SellSword, false)); // Es wird wieder möglich, ein Schwert zu kaufen; diesen Button aktivieren SellSword.AddClickListener(tr1::bind(&Button::SetActive, &BuySword, true)); // Geld erhalten SellSword.AddClickListener(tr1::bind(&Player::EarnMoney, &MyPlayer, 254)); // Schwert aus dem Inventar entfernen SellSword.AddClickListener(tr1::bind(&Player::RemoveItem, &MyPlayer, Inventory::Sword)); // Meldung anzeigen SellSword.AddClickListener(tr1::bind(&ShowMessage, "Schwert verkauft!"));
Okay. Nun gibt es ja noch die Schaltfläche "Alles verkaufen", wodurch Schwert, Schild und Rüstung verkauft werden. Hier ist eben
Click()
praktisch, weil man einfach die Aktionen der anderen drei "Verkaufen"-Buttons aufrufen kann:Button SellEverything("Alles verkaufen"); SellEverything.AddClickListener(tr1::bind(&Button::Click, &SellSword)); SellEverything.AddClickListener(tr1::bind(&Button::Click, &SellShield)); SellEverything.AddClickListener(tr1::bind(&Button::Click, &SellArmor));
Und schon hat man die Funktionalität!
Ich glaube, du nimmst "Click" etwas zu wörtlich. Eigentlich geht es mir vor allem ums Aufrufen der Listener. Du kannst du die Funktion ja auch "CallListeners" oder ähnlich nennen. Mit "AddClickListener" bin ich übrigens auch nicht 100% zufrieden, aber die Aufgabe sollte klar sein. Ich denke, das Standardverhalten bei
Click()
ist sinnvoll ohne Animation, man könnte aber einen entsprechenden Parameter einrichten.Wenn du kein
Click()
hast, sehe ich folgende Möglichkeiten:- Alle Funktionen, die den einzelnen Gegenständen zugeordnet sind, einzeln zuweisen. Schlecht wegen Codeduplizierung und leicht möglichen Inkonsistenzen bei der Wartung.
- Gesamte Aktionen auf den Verkauf der einzelnen Gegenstände in jeweils eine Funktion auslagern, sodass schliesslich drei Funktionen vorhanden sind. Schlecht, weil man die Möglichkeit, mehrere Funktionen zu registrieren, nicht ausnutzt und
std::tr1::bind()
ebenfalls nicht. Ausserdem werden die Funktionen recht gross mit entsprechend vielen Parametern und müssen sich um GUI und Logik kümmern. - Vor dem Aufruf von
AddClickListener()
für die einzelnen Gegenstände den entsprechenden Funktor zusätzlich an einem anderen Ort speichern. So braucht man zwar keinen Zugriff mehr auf die einzelnen Buttons, dupliziert aber unnötigerweise Information und nimmt die damit verbundenen Probleme in Kauf. - Auf Observer-Pattern ganz verzichten und in prozedurale Muster zurückfallen ("ask, don't tell").
Alle vier Möglichkeiten sind für mich Rückschritte und machen den Code komplexer.
-
Nexus schrieb:
Gut, dann möchte ich das angesprochene Beispiel verdeutlichen.
ne ne ne, ganz schlecht.
Was wenn du zB einn click sound auf einen Button hast?
Ne, du willst bei BuyAll einen anderen Sound als 3 mal kaufen habenWofür man die click() Methoden braucht ist für Agenten. zB wenn ich einen funktionstest implementiere, dann geh ich durch die fenster und simuliere user eingaben. und dann muss ich einen click simulieren und das geht entweder darüber dass ich die maus auf dem element positionieren und dem system ein click sende, oder aber ich rufe direkt click auf, was viel praktischer ist.
solche agenten braucht man aber nur selten, und functional testing ist jetzt spontan das einzige was mir dazu einfällt...
schaden tun die methoden aber nicht. aber bitte bitte bitte nie programmlogik über das view laufen lassen. Das view ist nur ein interface für den user, keins für den programmierer...
-
Shade Of Mine schrieb:
aber bitte bitte bitte nie programmlogik über das view laufen lassen. Das view ist nur ein interface für den user, keins für den programmierer...
Aber wie gesagt kommen mir alle Alternativen noch schlimmer vor. Wie würdest du denn sowas sauber implementieren?
-
Nexus schrieb:
Shade Of Mine schrieb:
aber bitte bitte bitte nie programmlogik über das view laufen lassen. Das view ist nur ein interface für den user, keins für den programmierer...
Aber wie gesagt kommen mir alle Alternativen noch schlimmer vor. Wie würdest du denn sowas sauber implementieren?
Jeder Button ruft eine Methode vom Controller auf.
BuySwordButton ruft BuySword() auf. bzw. bekommt das callback eben bei der erstellung.
Die Idee ist ja, dass du den Button einfach wegnehmen kannst und durch was anderes ersetzen kannst: zB könntest du scripting des spiels erlauben und dann will man keine clicks auf die buttons sondern direkt ins system rein.
jedenfalls hast du ja trigger auf den buttons. es passiert ja etwas wenn du ihn clickst. es kommt ein sound, es kommt eine animation etc. deshalb ist ein click auf buyAll eben nicht das selbe wie ein click auf alle buttons einzeln.
je nachdem wie das view aussieht, ruft der buyAll button wirklich nur direkt den controller auf, oder aber das view kennt die einzelnen callbacks und ruft sie bei buyAll selber alle auf.
es hängt halt wirklich von der struktur ab die du hast. und ja, manchmal muss man ein bisschen code verdoppeln. das view und der controller/model sind eben getrennte einheiten.
PS:
den functor bei addClickListener musst du ja nicht extra speichern, du machst zB:buySwordButton.addListener(buySword);
buyAllButton.addListener(buySword);und fertig.
-
Das klingt wirklich einleuchtend. Ich hab mir die ganze Angelegenheit wohl etwas zu schlimm vorgestellt. Allerdings muss ich auch zugeben, dass ich in der GUI-Programmierung noch nicht wahnsinnig viel Erfahrung habe – das ist auch ein Grund für mein kleines Framework.
Zum Beispiel habe ich unter View die grafische Repräsentation des Buttons selbst verstanden, und dabei gar nicht mehr daran gedacht, dass die GUI selbst nur ein View ist und womöglich austauschbar sein soll. Ich bin halt davon ausgegangen, dass Buttons fix bleiben, sich höchstens die Implementierung oder Darstellung ändern könnte. Aber der Code wird natürlich noch flexibler, wenn sich die Logik überhaupt nicht nach irgendeiner Benutzerschnittstelle richtet.
Ich habe halt einfach das Gefühl, dass man wieder einiges an zusätzlichem Code und Komplexität braucht, nur um "schönes" Design zu haben. Klar kann sich das oft lohnen und man braucht es sogar, aber für kleinere Dingen geht es teilweise vielleicht besser ohne. Ich bin diesbezüglich etwas pragmatisch und habe manchmal so meine Mühe mit Flexibilität, die ich nicht (immer) brauche.
Auf jeden Fall vielen Dank für die Erklärung, ich werde mein Konzept nochmals überdenken. Möglicherweise werde ich doch auf
Click()
verzichten...
-
Nexus schrieb:
Allerdings muss ich auch zugeben, dass ich in der GUI-Programmierung noch nicht wahnsinnig viel Erfahrung habe – das ist auch ein Grund für mein kleines Framework.
Ich sagte ja schon: Schau dir halt einfach andere Frameworks an. Da lernst du mehr, wie wenn du ein eigenes Framework nach deinen Vorstellungen baust
Die Aufrechterhaltung von Ordnung ist keine Zensur.
-
@€ schrieb:
Ich sagte ja schon: Schau dir halt einfach andere Frameworks an. Da lernst du mehr, wie wenn du ein eigenes Framework nach deinen Vorstellungen baust
Es ist ja nicht so, dass ich bisher noch kein GUI-Framework angeschaut hätte. Aber bei gewissen Sachen frage ich lieber hier nochmals nach oder überlege mir selbst eine Lösung – nicht zuletzt, weil andere Frameworks, nur weil sie verbreitet sind, noch lange nicht gut sein müssen. Gerade in C++ gibt es ja Beispiele, die immer noch auf Makros schwören und moderne Sprachmittel wie Templates oder Namensräume meiden.
Im Gegensatz zu fast allen GUI-Bibliotheken, die ich mir bis jetzt angeschaut habe, möchte ich dem Benutzer z.B. die Möglichkeit geben, Widgets auf dem Stack anzulegen und selbst zu verwalten. Mal schauen...
-
Nexus schrieb:
Das klingt wirklich einleuchtend. Ich hab mir die ganze Angelegenheit wohl etwas zu schlimm vorgestellt. Allerdings muss ich auch zugeben, dass ich in der GUI-Programmierung noch nicht wahnsinnig viel Erfahrung habe – das ist auch ein Grund für mein kleines Framework.
Wenn du mit GUI Programmierung wenig Erfahrung hast, dann solltest du GUI Frameworks verwenden, und nicht selbst eins schreiben!
Woher willst du sonst wissen, was man oft braucht, was selten, was Dinge sind die sich eingebürgert haben und auch einfach gut so sind, was verbesserungswürdig etc.?
"Ich lerne jetzt X indem ich ein X-Framework/eine X-Library schreibe" halte ich für den komplett verkehrten Ansatz.
-
Es ist ja nicht das Gleiche, ein GUI-Framework anzuwenden und eins zu schreiben. Beim Anwenden gibt es einfach sehr viele Dinge, welche im Hintergrund ablaufen und von denen man gar nie etwas mitbekommt. Mit "GUI-Programmierung" meinte ich vor allem diesen Teil. Ich interessiere mich zum Beispiel dafür, wie man ein Event-System aufbaut, wie eine Klassenhierarchie von GUI-Komponenten gestaltet werden kann, welche Design-Patterns wo eingesetzt werden und solche Sachen. Von der Implementierung erfährt man nicht besonders viel, wenn man nur fertige Bibliotheken benutzt.
Ich habe im Übrigen nicht vor, auf die Schnelle mal eben die Super-Library zu basteln. Da bin ich durchaus realistisch. GUIs für den "Hausgebrauch" gibt es genügend, erst kürzlich hat z.B. Artchi seine AlgierLib veröffentlicht. Da ich aber oft mit SFML programmiere und es dort nicht wirklich GUIs gibt, welche meinen Ansprüchen genügen, habe ich gedacht, ich könnte mir selbst was schreiben. Die Funktionalität wird nicht weltbewegend sein, im Zentrum stehen Klassen und Funktionen für alltägliche Probleme wie eben Schaltflächen.
Mein Problem besteht vor allem darin, dass ich noch nicht sehr viel Erfahrung habe, was grössere Projekte und entsprechende Designprobleme anbelangt. Ich habe vor etwa 4 Jahren mit der C++-Programmierung begonnen und war da totaler Anfänger. Vor etwa zwei Jahren habe ich "richtig" angefangen. Vorher programmierte ich nur ab und zu, und der Grossteil der Sprachmittel aus C++ war mir gänzlich unbekannt. Inzwischen kenne ich mich mit der Sprache einigermassen aus, und will mich momentan vermehrt Dingen wie OOD zuwenden. Ich habe schon einiges an Theorie angeschaut, jetzt scheint es mir naheliegend, mich an einem entsprechenden Projekt zu versuchen. Mit einer GUI habe ich gerade auch die Möglichkeit etwas zu entwickeln, das ich wiederverwenden kann und das mir und möglicherweise anderen Leuten später etwas bringt. Das ist besser als jedes gekünstelte Projekt, das nur zum Lernen dient und nachher in der Ecke verstaubt. Ausserdem ist ein GUI-Framework etwas, wo man sehr schön viele objektorientierte Techniken und Entwurfsmuster kombinieren kann.
Deshalb bin ich auch sehr froh über Ratschläge von erfahrenen Programmierern aus diesem Forum und stelle hier ab und zu Fragen. Ich hoffe, du kannst meine Beweggründe ungefähr nachvollziehen.
-
Nexus schrieb:
Ich habe halt einfach das Gefühl, dass man wieder einiges an zusätzlichem Code und Komplexität braucht, nur um "schönes" Design zu haben. Klar kann sich das oft lohnen und man braucht es sogar, aber für kleinere Dingen geht es teilweise vielleicht besser ohne. Ich bin diesbezüglich etwas pragmatisch und habe manchmal so meine Mühe mit Flexibilität, die ich nicht (immer) brauche.
Das ist das ewige Problem
Entweder jetzt sofort fertig sein und bei Änderungen in Probleme kommen oder erst später fertig werden und dafür auf Änderungen gelassen reagieren können.
Da muss man immer ein Mittel finden und das ist nicht leicht (weil es keine goldenen regeln dafür gibt).
Und was natürlich aufs erste etwas abschreckt: gute kapselung verdoppelt code. Hört sich so aber tragischer an als es ist. denn du verdoppelst keine logik, sondern eben nur code.