Zugriff auf mehrere Interfaces über einzelnen Zeiger



  • Ich würde eher einen Adapter dazwischenschalten, bevor ich die View-Klasse zum Template machen und/oder ihr einen Template-Ctor verpasse.

    Die View-Klasse zum Template zu machen wäre auch doof denke ich, da man ja vermutlich das "betrachtete" Widget zur Laufzeit auswechseln können soll. Und wenn dann nicht alle Widget zwischen denen man wechseln will den selben Typ haben, geht das nicht mehr so ohne Weiteres.

    class TextViewSourceBase
    {
    public:
        // einfache Variante:
        virtual StringAttributable& GetStringAttributable() = 0;
        virtual Widget& GetWidget() = 0;
    
        // die aufwendigere Variante wäre alle Funktionen weiterzuleiten
    };
    
    template <class T>
    class TextViewSource : public TextViewSourceBase
    {
        // kann man dann spezialisieren wie man will, evtl. mit Hilfe von 
        // boost::is_base_and_derived und diversen Template-Tricks
    };
    
    class TextView
    {
    public:
        explicit TextView(shared_ptr<TextViewSourceBase> const& source);
        void SetSource(shared_ptr<TextViewSourceBase> const& source);
    };
    

    Das Doofe an der Sache: man kann die Adapter nicht by-value rumreichen, da der konkrete Typ nicht feststeht. D.h. man muss entweder klonen oder Ownership übergeben (finde ich immer doof) oder eben Shared-Ownership verwenden (shared_ptr etc.).



  • Mit dem Template hast du Recht, direkt hätte es ein paar Nachteile. Beim Adapter sehe ich allerdings keinen grossen Unterschied mehr zu den beiden rohen, separaten Pointers. Man hat da halt ein einzelnes Objekt mit eigener Speicherverwaltung (als shared_ptr oder so eingepackt). Jedoch sind bei mir Speicherverwaltung und Besitzverhältnisse nicht gerade ein Problem, da vorläufig nur passive Verweise im Spiel sind.



  • Dann nen Template-Ctor mit enable_if<is_base_and_derived<A, X>::value && is_base_and_derived<B, X>::value> (sinngemäss).



  • Die Vererbungsbeziehung könnte doch implizit geprüft werden, wenn ich im Konstruktor zwei automatische Upcasts X* -> A* und X* -> B* habe...



  • Wenn du keine weiteren Ein-Parameter-Template-Ctors brauchst, dann ja.



  • Stimmt, SFINAE ist sonst nicht dabei. Danke für den Hinweis.



  • Wie sieht denn ein Aufruf in der TextView auf den Button aus?



  • Zum Beispiel folgendermassen. myWidgetController und myStringController wären die beiden Basisklassenzeiger. Die Update...() -Methoden werden bei Eigenschafts-Änderungen des Button aufgerufen.

    void TextView::UpdateSize()
    {
        myText.SetSize(myWidgetController->GetSize());
    }
    
    void TextView::UpdateString()
    {
        myText.SetString(myStringController->GetString());
        myText.SetTextAlignment(myStringController->GetTextAlignment());
    }
    


  • @Nexus: nur so ein Gedanke zwischendrin: Soweit ich mal in GUI-Frameworks reingesehen hab (mache sonst meist embedded-Software...), haben doch meist alle GUI-Klassen eine gemeinsame Wurzelklasse, sozusagen das Ur-GUI-Element. Dadurch sind sie alle über Pointer in einer Baumstruktur ansprechbar (so meine ich mich zu erinnern, dass es z.B. eine show( )-Methode gibt, kann sein bei QT oder .NET).

    In Deinem Fall wäre das in etwa das, was Widget machen soll, denn eine Position, Größe, Sichtbarkeit, "Aktiviertheit" oder so trifft doch auf die allermeisten GUI-Elemente zu, oder? Also könnte das doch in eine Basisklasse?

    Im Gegensatz dazu ist bei String nachvollziehbar, dass dies eine Spezialisierung ist, ganz klar.



  • Genau, Widget ist diese Basisklasse. Wobei es noch weiter hinauf geht, wenn man elementare und Container-Komponenten unterscheidet. Doch alle elementaren GUI-Komponenten erben von Widget .

    Vielleicht ist der Begriff "Interface" hier nicht ganz richtig, da es sich nicht immer um abstrakte, komplett statuslose Klassen handelt. Aber das Prinzip sollte klar sein.



  • Ich meinte es eher auf die Art, dass auch StringAttributable von Widget erbt. In etwa so (bei mal angenommenen anderen "Attribut-Klassen" und Objektklassen wie Panel und Tool ):

    +-------------+
            __________________|   Widget    |
           /                  +-------------+
          /                  /       |       \ 
         /                  /        |        \
         |    +------------+   +-----------+   +-----------+
         |    | StringAttr |   | GraphAttr |   | SoundAttr |
         |    +------------+   +-----------+   +-----------+
         |         |      \       |
         |         |       \      |
    +-------+  +--------+   +-------+
    | Panel |  | Button |   | Tool  |            ....
    +-------+  +--------+   +-------+
    

    Die Attribut-Klassen erben virtuell von Widget . Dadurch können die eigentlichen Objekt-Klassen von keinem ( Panel ), einem ( Button ) oder mehreren ( Tool ) der Attribute erben, ohne die Basisklasse mehrmals drin zu haben.

    Zugriff wäre wie folgt möglich:

    1. Allgemeine Aufgaben wie show( )
    Angenommen, es gäbe einen show( ) -Aufruf an alle Elemente. Widget würde die Methode virtuell deklarieren und die Unterklassen sie implementieren. Alle Objekte können über einen Widget* -Pointer angesprochen werden (wie z.B. von TextView ). Button oder Tool wüßten ja, von welchen Klassen sie abgeleitet sind und rufen entsprechende Methoden ihrer Basisklassen auf. Dies wäre für TextView dann transparent.

    Oder:
    2. Gezieltes Ansprechen von Elementen mit gleichen Attributen.
    Da im Beispiel oben sowohl Button als auch Tool ein StringAttributable sind, können sie in einer Klasse TextView über einen Pointer von ebendiesem Typ referenziert werden. Das ist in etwa das, wie ich Deine Aufgabe von zu Beginn interpretiere. Gleichzeitig sind sie auch Widgets , d.h. Zugriff auf die Basisklasse ist ebenfalls möglich. Andere denkbare Klassen wie z.B. GraphView würden Objekte mit GraphAttributable ansprechen usw.

    Kann sein, dass ich Deine Intention bzw. den Grund der weitestmöglichen Trennung der einzelnen Klassen noch nicht so ganz begriffen habe. Aber dieser Ansatz wäre etwas, was mir sowohl OO-mäßig als auch durch Cast-Vermeidung sinnvoll erscheint.



  • Vielen Dank für den Vorschlag, das tönt schon mal recht interessant. Virtuelle Vererbung habe ich bisher höchst selten verwendet, vielleicht hat sie noch ein paar interessante Tricks parat 🙂

    Die Frage ist nun halt, ob StringAttributable wirklich immer ein Widget ist. Bei den bisherigen Beispielen schon, aber ob das so bleibt?

    Ich habe zum Beispiel gerade heute festgestellt, dass ich was grundsätzlich redesignen muss, um die Grösse einer GUI-Komponenten und die klickbare Fläche zu unterscheiden (z.B. bei CheckBox ). Mir ist dann sowas wie FloatRect GetClickableArea() const eingefallen, aber was mach ich bei Komponenten wie Scrollbars, die mehrere Klickflächen haben? 😉



  • Ich habe letztens eine Socket-Klasse geschrieben, wo alles ein Socket ist. Socket hat die elementaren Methoden wie open( ), close( ), read( ), write( ). Davon virtuell abgeleitet auf der einen Seite TCP und UDP , auf der anderen Server und Client . Darunter dann die eigentlichen TCPServerSocket, UDPClientSocket usw., die also von je zwei "Attributen" erben.
    Bislang sind es immer Sockets geblieben...

    Wenn man hierbei in der ersten Ebene nicht virtuell vererbt, haben die Attribute jeweils die Basisklasse drin und auf der unteren Ebene hat man diese dann doppelt.

    Ist eigentlich ganz einfach: Man deklariert nur (in Deinem Fall)

    class StringAttributable : virtual public Widget
    

    Bezüglich der Scrollbars: Naja, ein Scrollbar könnte ja wiederum z.B. aus mehreren einzelnen Elementen bestehen. Die haben alle GetClickableArea( ) , und die Bar sammelt die zusammen und gibt das als GetClickableArea( ) zurück.



  • minastaros schrieb:

    Ich habe letztens eine Socket-Klasse geschrieben, wo alles ein Socket ist. Socket hat die elementaren Methoden wie open( ), close( ), read( ), write( ). Davon virtuell abgeleitet auf der einen Seite TCP und UDP , auf der anderen Server und Client . Darunter dann die eigentlichen TCPServerSocket, UDPClientSocket usw., die also von je zwei "Attributen" erben.
    Bislang sind es immer Sockets geblieben...

    Bei solchen Designs muss man halt aufpassen, weil die Anzahl Klassen explodieren kann. Zumindest mag ich mich noch erinnern, dass Alexandrescu in Modern C++ Design sowas als Motivationsbeispiel für Policies genommen hat. 😉

    Aber die Idee mit virtueller Vererbung ist wirklich mal eine Überlegung wert.

    minastaros schrieb:

    Bezüglich der Scrollbars: Naja, ein Scrollbar könnte ja wiederum z.B. aus mehreren einzelnen Elementen bestehen. Die haben alle GetClickableArea( ) , und die Bar sammelt die zusammen und gibt das als GetClickableArea( ) zurück.

    Ja, so könnte ich es machen. Ähnlich wie beim Panel , das andere GUI-Komponenten beinhaltet. Naja, bis ich zur Scrollbar-Implementierung komme, vergeht eh noch einige Zeit.



  • weil die Anzahl Klassen explodieren kann.

    Das hielt sich aufgrund der vier Varianten noch in Grenzen, aber stimmt, die vermehren sich wie die Katzen...

    Nexus schrieb:

    Zumindest mag ich mich noch erinnern, dass Alexandrescu in Modern C++ Design sowas als Motivationsbeispiel für Policies genommen hat. 😉

    Richtig, aber das Buch wohnt erst seit einer Woche auf meinem Sofa bzw. Nachttisch. Habe immerhin schon Kapitel 1 durch, für die Sockets war es schon zu spät. Für die Templates fehlt mir noch die Erfahrung.


Anmelden zum Antworten