Designfrage: Abhängig vom Typ anderen Code ausführen



  • Abend!

    Sagen wir ich habe folgende Klassen: Eine abstrakte Basisklasse Shape und zig davon abgeleitete Subklassen wie Box, Circle etc.

    Nun schreibe ich einen Editor, in dem man die ganzen konkreten Shapes (Box, Circle etc.) mit der Maus anklicken kann. Die gerade selektierte Shape merke ich mir per Pointer: Shape* currentlySelectedShape.

    Das Problem ist nun: Immer wenn der User eine Shape anklickt, will ich in meinem Editor Eigenschaften der jeweiligen Shape anzeigen (zum Editieren). Klickt der User z.B. auf einen Circle, sieht man ein Panel, das alle Circle Eigenschaften (Position, Radius etc.) anzeigt. Klickt der User auf eine Box, wird ein anderes Panel angezeigt, das Position, Breite, Höhe etc. anzeigt.

    Wie mache ich das, dass abhängig vom Typ der Shape ein anderes Panel anzeigt wird? Mir ist klar, dass ich das per virtueller Methode in Shape lösen könnte, aber das Panel Zeugs ist ja GUI Zeugs und hat nichts in Shape zu suchen. Wie könnte ich das sonst machen?



  • Dafür nimmt man oft das Visitor Pattern.

    Ein typischer Nachteil ist, dass es schlecht erweiterbar ist und du quasi schon vorher alle Ausprägungen kennen musst. Ist aber oft verschmerzbar. In dem Fall wahrscheinlich auch. Wenn neue Typen dazu kommen, musst du die Visitor Schnittstelle anpassen. Aber komplett dynamische Ableitungen, die du nicht kennst, kommen hier wahrscheinlich nicht dazu.
    Bzw., evtl. hast du dann sowas wie PluginShape Ableitung, die aus irgendwelchen Plugins kommt, und kannst entweder für die ein generischen Plugin Pane anzeigen, oder die hat eine erweiterte Schnittstelle und kann selber Panes anzeigen.



  • vielleicht wäre auch ein bisschen selbstgebastelte reflection ne lösung, so zum beispiel:

    class shape
    {
    virtual void repaint() = 0;
    map<string, int> int_properties;
    add(string name, int &p)
    {
      int_properties[name] = &p;
    }
    set(string name, int v)
    {
      int *p(int_properties[name]);
      *p = v;
      repaint();
    }
    get_props(vector &a)
    {
    //  a mit allen nötigen informationen über die properties füllen
    // also das es ein int/double/string oder was auch immer ist
    }
    };
    
    class circle
    {
    int x,y,radius;
    circle()
    {
      add("x", x);
      add("y", y);
      add("radius", radius); 
    }
    // copyconstructor und so entsprechend 
    };
    

    die panel anzeige kann dann einfach auf die baseklasse zugreifen und sich alle properties laden und dann anzeigen.



  • Die Idee finde ich grundsätzlich auch nicht schlecht, würds dann aber wahrscheinlich wesentlich komplizierter aufziehen, als die gezeigte Beispielimplementierung. Das wär auf jeden Fall eine Überlegung wert, musst halt versuchen, möglichst genau abzuschätzen, ob du mit so einer generischen Lösung alles erschlagen kannst, was du brauchst/brauchen wirst.
    Ich würde spontan schätzen, ja.



  • Ja, kommt immer drauf an was man machen will bzw. braucht.

    Eine andere Möglichkeit wäre z.B. einfach nur jeder Shape Klasse eine ID zu verpassen. Dazu kann man entweder direkt typeid() verwenden, oder man macht ne eigene (virtuelle) "get type ID" Funktion, die dann nen int/ne GUID/... zurückgibt.

    Dann kann man sich ein Dictionary machen wo man Support-Objekte für die Shapes registrieren kann (key = Type-ID, value = Support-Objekt). Diese Support-Objekte können dann wiederrum sein was man will. Enwteder gleich Factories für Editor-Widgets. Oder sie können ein Interface wie das was jenz skizziert hat bereitstellen.

    Das wäre z.B. eine sinnvolle Lösung wenn man aus irgend einem Grund so wenig wie möglich "editorbedingte Artefakte" im "eigentlichen" Code drinnen haben will (und sonst nirgends mächtigere Reflection-Fähigkeiten braucht bzw. brauchen kann).

    Bzw. kann man es ähnlich der Lösung von jenz anpacken und die Klassen serialisierbar machen. Dabei müssen auch notgedrungen alle Informationen über die Shape ins Output-Archiv/Stream. Der Editor kann dann die Daten im Output-Archiv/Stream verwenden um seine Widgets zu befüllen. Und zum Updaten der Objekte diese einfach wieder aus dem modifizierten Archiv/Stream deserialisieren.



  • kuku schrieb:

    Das Problem ist nun: Immer wenn der User eine Shape anklickt, will ich in meinem Editor Eigenschaften der jeweiligen Shape anzeigen
    ...
    Wie mache ich das, dass abhängig vom Typ der Shape ein anderes
    Panel anzeigt wird? Mir ist klar, dass ich das per virtueller Methode in Shape
    lösen könnte, aber das Panel Zeugs ist ja GUI Zeugs und hat nichts in Shape zu
    suchen. Wie könnte ich das sonst machen?

    Irgendwie versteh ich da das problem nicht so recht.
    Wenn jemand da was anklickt dann ist das ja auch GUI.
    und wenn das object ein z.b. circle ist,
    dann wusstest du bei der erstellung auch schon das du einen cirle erstellst. also spendiere ihm z.b. einen zeiger auf ein object base_panel.

    struct base_panel
    {
       virtual create_child_controls();
       ...
    };
    
    struct circle_panel : base_panel
    {
       virtual create_child_controls()
       {
          /* erstelle controls die du fuer cirle brauchst */
       }
    };
    

    wenn nun jemand auf cirle klickt dann brauchst nur noch ueber den base_panel-pointer dein panel erstellen lassen.



  • @Meep Meep
    Es geht doch gerade darum dass die Shapes nix von den Panels (Views, Controllern, ...) wissen sollen.

    Jetzt könnte man sagen: der Editor erzeugt doch die ganzen Shapes, also kann er auch ein Dictionary (Map) mitpflegen, in dem Metadaten zu den Shapes hinterlegt sind. z.B. welche Art von Panel erstellt werden muss wenn man die Shape editieren möchte.
    (Oder war dein Beitrag sogar so gemeint? Ich habe das "spendiere ihm z.b. einen zeiger" auf jeden Fall so verstanden dass du den Zeiger in der Shape ablegen willst. Was ja gerade nicht erwünscht ist, weil intrusive.)

    Je nach Art der Anwendung könnte das sogar ganz gut funktionieren. *

    Es könnte aber sein dass man Codeteile/Algorithmen hat die...
    - man im Editor verwenden möchte
    - die aber nicht vom Editor abhängig sein sollen
    - die z.B. beliebige Shapes erzeugen können (die sie dann z.B. einfach per unique_ptr<Shape> zurückgeben)
    In dem Fall hätte man wieder ein Problem. Weil der Editor dann einfach nicht wissen kann welchen Typ Shape er zurückbekommen hat.

    So ein Codeteil könnte z.B. eine Serialisierungs-/Deserialisierungsschicht sein. Da könnte ja auch der Wunsch bestehen die nicht abhängig vom Editor zu machen, und umgekehrt den Editor nicht vom Format der Serialisierung abhängig zu machen.
    Ich weiss, das schlägt sich mit meinem Beispiel, wo ich vorgeschlagen habe dass der Editor die serialisierten Daten verwendet um zu gucken mit welcher Shape er es zu tun hat. In dem Fall wäre der Editor nämlich abhängig vom Format der Serialisierung. Unterschiedliche Anforderungen -> unterschiedliche Lösung 🙂

    *: Ein Vorteil dieser Lösung wäre dass man dafür überhaupt keine virtuellen Funktionen in den Shapes braucht. Bzw. generell keinerlei Support für irgendwas in den Shapes. Wobei ich bei dem Beispiel eher davon ausgehe dass man sowieso virtuellen Funktionen in einer gemeinsamen Basisklasse haben wird. Und dann kann man auf jeden Fall typeid() verwenden.



  • hustbaer schrieb:

    Jetzt könnte man sagen: der Editor erzeugt doch die ganzen Shapes, also kann er auch ein Dictionary (Map) mitpflegen, in dem Metadaten zu den Shapes hinterlegt
    sind. z.B. welche Art von Panel erstellt werden muss wenn man die Shape editieren möchte.
    (Oder war dein Beitrag sogar so gemeint? Ich habe das "spendiere ihm z.b. einen zeiger" auf jeden Fall so verstanden dass du den Zeiger in der Shape ablegen
    willst. Was ja gerade nicht erwünscht ist, weil intrusive.)

    mit Circle meinte ich eigendlich auch das grafische Circle object auf das man klickt. ich weiß nicht wie der editor aussieht und ob Circle ein button oder sonst was (GUI gesehen) ist.
    in dem fall ist der pointer dann auch sowas aehnliches wie eine ID, nur halt gleich mit funktion. man koennte auch einen func-pointer auf eine factory speichern und beim click event, diese aufrufen.

    und das sollte dann unabhaengig vom editor sein.
    klar, haengt auch davon ab, wie die GUI programmiert ist.

    Meep Meep



  • @mechanics: ja, das ganze müsste/sollte man natürlich wesentlich komplexer aufbauen.

    aber das schadet ja nicht, so ne reflection kann man ja immer wieder mal gebrauchen finde ich. 🙂

    j



  • @Meep Meep
    Dann sind wir ja erst recht ganz auf der GUI Seite.
    Die Frage ist dann also: wie erzeugst du diesen "graphischen Circle" überhaupt erst, wenn alles was du bekommst nen Shape* ist?



  • hustbaer schrieb:

    @Meep Meep
    Dann sind wir ja erst recht ganz auf der GUI Seite.
    Die Frage ist dann also: wie erzeugst du diesen "graphischen Circle" überhaupt erst, wenn alles was du bekommst nen Shape* ist?

    ich weiß leider nicht wie er das gemaht hat, bzw. wie es aussieht.
    ich habe auch ehrlichgesagt keine ahnung wie ich mir das vorstellen soll.
    daher kann ich dir da keine antwort geben.

    ich bin einfach davon ausgegangen das alles grafisch ist und man einen (nicht-GUI-object) Circle an die grafische anzeige eines Circle in irgendeine art und weise bindet.

    Meep Meep


Log in to reply