Designfrage: MVC + Composite



  • Hallo,

    ich habe eine eher allgemeine Designfrage.

    Also, ich habe eine Klassenhierarchie mit geometrischen Items, etwa so:

    Item
    |____GeoItem
    |_____Circle
    |_____Polygon
    |_____Polyline
    |_____Point
    ...

    Jedes Item hat dabei Zugriff auf seine Kinder, etwa so:

    class Item
    {
    private:
        QString mID;
        QString mName;
    
        Item *mParent;           // parent item
        Item *mRoot;             // root item
        QList<Item*> mChildren;  // children
    
        QPen mPen;
        QBrush mBrush;
    
    public:
        Item(Item *parent = 0);
        virtual ~Item()
    
    #ifndef NOGUI
        virtual void renderSelf(QPainter *painter) const;
    
        setBrush(const QBrush& b);
        setPen(const QPen& p);
    
        virtual QPen pen() const;
        virtual QBrush brush() const;
    #endif
    
        QString id() const;
        [...]
    
        bool isChild(const Item* i) const;
        void addChild(Item* child);
        QList<Item*> children() const;
    
    [...]
    };
    

    Die Unterklassen überschreiben dann jeweils die virtuellen Methoden, um sich selbst zu zeichnen (render) oder eigene Pens/Brushs zurückzugeben (pen()/brush()), also z.B.

    class Polygon : public Item
    {
    private:
        QVector<QPoint> mPolygon;
    [...]
    public:
        Polygon(Item *parent = 0);
    
    #ifndef NOGUI
        virtual void render(QPainter *painter) const;
    
        virtual QPen pen() const;
        virtual QBrush brush() const;
    #endif
    [...]
    };
    

    Ok, jetzt möchte ich gerne zusätzlich zum Composite-Pattern der Items gerne eine Trennung von Model und View, d.h. die GUI-Teile (zwischen den #ifdef NOGUI/#endif) sollten möglichst raus aus den Items...

    Nur weiß ich nicht so recht, wie man das trennen kann, eine Möglichkeit, die mir eingefallen ist, wäre den ganzen Composite-Baum nochmals zu replizieren in der View:

    ItemView
    |_________CircleView
    |_________PolygonView
    |_________PolylineView
    [...]

    und dann etwa eine Membervariable in den Model-Klassen zu halten:

    class ItemView
    {
    public:
        ItemView(ItemView *parent = 0);
    
        virtual void render(QPainter *painter) const;
    [...]
    };
    
    class PolygonView : public ItemView
    {
    public:
        PolygonView(ItemView *parent = 0) : ItemView(parent)
        {
           [...]   
        }
    
        virtual void render(QPainter *painter) const
        {
            // Code zum zeichnen eines Polygon
        }
    };
    
    class Item
    {
    [...]
    private:
    #ifndef NOGUI
         ItemView *mItemView;
    #endif
    [...]
    public:
    #ifndef NOGUI
        virtual void render(QPainter *painter) const
        {
            mItemView->render(painter);
        }
    #endif
    };
    
    class Polygon : public Item
    {
    public:
        Polygon(Item *parent = 0) : Item(parent)
        {
            mItemView = new PolygonView(parent->itemView());
        }
    };
    

    aber das heißt, daß alle Klassen repliziert werden müssen.

    Eine weitere Möglichkeit, die mir eingefallen ist, wäre ein Visitor, etwa so:

    class Visitor
    {
    public:
        virtual void renderCircle(Circle* circle);
        virtual void renderPolygon(Polygon* poly);
        [...]
    };
    
    class ConcreteVisitor1 : public Visitor
    {
    public:
        virtual void renderCircle(Circle* circle);
        virtual void renderPolygon(Polygon* poly);
    };
    
    class Item
    {
    public:
    [...]
        virtual void accept(Visitor *v) = 0;
    };
    
    class Polygon : public Item
    {
    [...]
        virtual void accept(Visitor* v)
        {
            v->renderPolygon(this);
        }
    [...]
    };
    

    allerdings müsste ich dann in den konkreten Visitor-Klassen für alle Items eine render-Methode implementieren.

    Vll. gibt es noch eine andere Strategie Model und View zu trennen, bin für alle Tipps & Hinweise zur gemeinsamen MVC + Composite Verwendung dankbar,

    AlGaN



  • Wieso siehst du das als Composite? Ein Item besteht doch nicht aus einem Polygon, Circle und Point. Das ist doch nur Vererbung. Wenn du zwei Vererbungshierarchien implementieren willst (Item->Polygon ... und nondrawable->withGui) dann schau dir mal das brückenmuster an.

    Edit: Ah, du siehst Item als Composible Item o.ä. an richtig?



  • Da dein Frage zwar allgemein sein mag, dein Ansatz aber konkret Qt-Klassen verwendet:
    Mit dem Model/View-Framework von Qt4 hast du dich schon auseinander gesetzt?



  • @witte:

    Item ist als Oberklasse gedacht und wird nicht wirklich instanziiert, sollte eigentlich abstrakt sein. Es bildet aber die Baumstruktur meiner Zeichnung ab, d.h. ein Item kann mehrere Kinder enthalten (s. Member mChildren : QList<Item*>). In Wahrheit ist die Hierarchie natürlich tiefer, also z.B.:

    Item
    |____ GroupItem
    |         |
    |         [...]
    |
    |____ DrawableItem
                |__________Polygon
                |__________Circle
                [...]
    

    Also GroupItem kann eben mehrere DrawableItems zusammenfassen und implementiert noch evt. Properties einer Gruppe (Mittelpunkt, BoundingBox etc.)

    class GroupItem : public Item
    {
    public:
        GroupItem(Item *parent = 0);
    
        QPointF center() const;
    
        QRectF boundingRect() const;
    [...]
    };
    

    Beim Brückenmuster (swoeit ich das kenne, geht es ja um die Trennung von Abstraktion und Implementierung, aber dazu müsste ich doch auch zwei Vererbungshierarchien implementieren, etwa so, oder?

    Item <>---------------------------- ItemView
    |                                      |
    |____DrawableItem                      |__ DrawableItemView
         |_____________ Circle                 |_________________ CircleView
         |_____________ Polygon                |_________________ PolygonView
         [...]                                 [...]
    

    mit einem Member in der Item-Klasse:

    class Item
    {
    public:
        virtual void render(QPainter *painter) const {
            mView->render(painter);
        }
    
    [...]
    private:
        ItemView *mView;
    };
    

    Das wäre ja im Prinzip genau das, was ich mit Replizieren gemeint hatte...

    @nogu:
    Ich hab mich noch nicht tiefer mit Model/View-Framework von Qt beschäftigt, anhand der Bsp. aber hatte ich den Eindruck, dass es auf bestimmte Models/Views (Tabellen, Trees etc.) abgestimmt ist (hab mich jetzt aber nich wirklich eingehender damit beschäftigt), außerde, wollte ich Overheads vermeiden, da das Rendering der Items möglichst schnell sein muss...


Log in to reply