Entwurfsmuster für polymorphes Verhalten bei gleichzeitiger Trennung der Bibliothek

10

Angenommen, ich habe eine Hierarchie von ItemKlassen : Rectangle, Circle, Triangle. Ich möchte sie zeichnen können, daher besteht meine erste Möglichkeit darin Draw(), jedem eine virtuelle Methode hinzuzufügen :

class Item {
public:
   virtual ~Item();
   virtual void Draw() =0; 
};

Ich möchte jedoch die Zeichnungsfunktionalität in eine separate Zeichnungsbibliothek aufteilen, während die Kernbibliothek nur die grundlegenden Darstellungen enthält. Ich kann mir einige Möglichkeiten vorstellen:

1 - A DrawManager, das eine Liste von Items erstellt und verwenden muss dynamic_cast<>, um herauszufinden, was zu tun ist:

class DrawManager {
  void draw(ItemList& items) {
    FOREACH(Item* item, items) {
       if (dynamic_cast<Rectangle*>(item)) {
          drawRectangle();
       } else if (dynamic_cast<Circle*>(item)) {
          drawCircle();
       } ....
    }
  }
};

Dies ist nicht ideal, da es auf RTTI basiert und eine Klasse dazu zwingt, alle Elemente in der Hierarchie zu kennen.

2 - Der andere Ansatz besteht darin, die Zeichnungsverantwortung auf eine ItemDrawerHierarchie ( RectangleDrawerusw.) zu verschieben:

class Item {
   virtual Drawer* GetDrawer() =0;
}

class Rectangle : public Item {
public:
   virtual Drawer* GetDrawer() {return new RectangleDrawer(this); }
}

Dadurch wird die Trennung von Bedenken zwischen der Basisdarstellung der Elemente und dem Code zum Zeichnen erreicht. Das Problem ist jedoch, dass die Elementklassen von den Zeichnungsklassen abhängig sind.

Wie kann ich diesen Zeichnungscode in eine separate Bibliothek aufteilen? Ist die Lösung für die Artikel, eine Factory-Klasse mit einer Beschreibung zurückzugeben? Wie kann dies jedoch definiert werden, damit die Core-Bibliothek nicht von der Draw-Bibliothek abhängig ist?

the_mandrill
quelle

Antworten:

3

Schauen Sie sich das Besuchermuster an .

Es ist beabsichtigt, einen Algorithmus (in Ihrem Fall eine Zeichnung) von einer Objektstruktur zu trennen, mit der er arbeitet (Elemente).

Ein einfaches Beispiel für Ihr Problem wäre also:

class Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};

class Rectangle : public Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};

class Circle : public Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};


class Visitable
{
public:
    virtual void accept(Rectangle& r);
    virtual void accept(Circle& c);
};

class Drawer : public Visitable
{
public:
    void accept(Rectangle& r)
    {
        // draw rectangle
    }   
    void accept(Circle& c)
    {
        // draw circle
    }
};


int main()
{
    Item* i1 = new Circle;
    Item* i2 = new Rectangle;

    Drawer d;
    i1->visit(d); // Draw circle
    i2->visit(d); // Draw rectangle

    return 1;
}
heiße Kartoffel
quelle
Interessant - ich hätte nie darüber nachgedacht, einen Besucher mit Überladung zu verwenden, um Polymorphismus zu erreichen. Würde diese Überlastung jedoch zur Laufzeit korrekt sein?
the_mandrill
Ja, es würde richtig überladen. Siehe bearbeitete Antwort
hotpotato
Ich kann sehen, dass das funktioniert. Es ist jedoch eine Schande, dass jede abgeleitete Klasse die visit()Methode implementieren muss .
the_mandrill
Dies veranlasste mich, etwas mehr zu suchen, und ich habe jetzt die Implementierung von Lokis Besuchermuster gefunden, das genau das zu sein scheint, wonach ich suche! Ich war mit dem Besuchermuster in Bezug auf den Besuch von Knoten in Bäumen vertraut, hatte es aber nicht abstrakter gesehen.
the_mandrill
2

Die von Ihnen beschriebene Aufteilung in zwei parallele Hierarchien ist im Wesentlichen das Brückenmuster .

Sie befürchten, dass die verfeinerte Abstraktion (Rectangle usw.) von der Implementierung (RectangleDrawer ) abhängt , aber ich bin mir nicht sicher, wie Sie dies vollständig vermeiden können.

Sie können sicherlich eine abstrakte Factory bereitstellen, um diese Abhängigkeit zu lösen, aber Sie müssen der Factory noch eine Möglichkeit mitteilen, welche Unterklasse erstellt werden soll, und diese Unterklasse hängt immer noch von der spezifischen verfeinerten Implementierung ab. Das heißt, auch wenn Rectangle nicht von RectangleDrawer abhängt, muss die Fabrik angewiesen werden, einen zu bauen, und RectangleDrawer selbst hängt immer noch von Rectangle ab.

Es lohnt sich auch zu fragen, ob dies eine nützliche Abstraktion ist. Ist das Zeichnen eines Rechtecks ​​so schwierig, dass es sich lohnt, eine andere Klasse einzuordnen, oder versuchen Sie wirklich, die Grafikbibliothek zu abstrahieren?

Nutzlos
quelle
Ich dachte, ich hätte dieses Muster gesehen, bevor ich die parallelen Hierarchien hatte - danke, dass ich mich darüber Gedanken gemacht habe. Ich möchte jedoch die Grafikbibliothek abstrahieren. Ich möchte nicht, dass die Kernbibliothek von der Grafikbibliothek abhängt. Angenommen, die Kernanwendung verwaltet Formobjekte und die Bibliothek zum Anzeigen ist ein Add-On. Bedenken Sie jedoch, dass dies wirklich keine Anwendung zum Zeichnen von Rechtecken ist - ich verwende sie nur als Metapher.
the_mandrill
Was ich denke bin , ist , dass RectangleDrawer, CircleDrawerusw. nicht der nützlichste Weg zu abstrahieren der Grafikbibliothek sein kann. Wenn Sie stattdessen eine Reihe von Grundelementen über eine reguläre abstrakte Schnittstelle verfügbar machen, wird die Abhängigkeit trotzdem aufgehoben.
Nutzlos
1

Schauen Sie sich die Wertesemantik und den auf Konzepten basierenden Polymorphismus an .

Diese Arbeit hat meine Sicht auf Polymorphismus verändert. Wir haben dieses System in wiederholten Situationen sehr effektiv eingesetzt.

Bill Door
quelle
Das sieht faszinierend aus - scheint in die Richtung zu gehen, die ich will
the_mandrill
1
Die Verbindung ist unterbrochen. Aus diesem Grund wird von Antworten abgeraten, die im Wesentlichen nur aus Links bestehen.
Ajb
0

Der Grund, warum Sie ein Problem haben, ist, dass Objekte sich nicht selbst zeichnen sollten. Etwas richtig zu zeichnen hängt von einer Milliarde Staatsstücken ab, und ein Rechteck wird keines dieser Teile haben. Sie sollten ein zentrales grafisches Objekt haben, das den Kernzustand wie Backbuffer / Tiefenpuffer / Shader / etc darstellt, und ihm dann eine Draw () -Methode geben.

DeadMG
quelle
Ich stimme zu, dass Objekte sich nicht selbst zeichnen sollten, daher der Wunsch, diese Fähigkeit in eine andere Klasse zu verschieben, um eine Trennung der Anliegen zu erreichen. Angenommen, diese Klassen waren Dogoder Cat- das Objekt, das für das Zeichnen verantwortlich ist, ist viel komplexer als das Zeichnen eines Rechtecks.
the_mandrill