Progetta il modello per il comportamento polimorfico consentendo la separazione delle librerie


10

Diciamo che ho una gerarchia di Itemclassi: Rectangle, Circle, Triangle. Voglio essere in grado di disegnarli, quindi la mia prima possibilità è aggiungere un Draw()metodo virtuale a ciascuno:

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

Tuttavia, voglio dividere la funzionalità di disegno in una libreria Draw separata mentre la libreria Core contiene solo le rappresentazioni di base. Ci sono un paio di possibilità che mi vengono in mente:

1 - A DrawManagerche prende un elenco di se Itemdeve usare dynamic_cast<>per capire cosa fare:

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

Questo non è l'ideale in quanto si basa su RTTI e impone a una classe di essere a conoscenza di tutti gli elementi nella gerarchia.

2 - L'altro approccio consiste nel rinviare la responsabilità di attirare una ItemDrawergerarchia ( RectangleDrawer, ecc.):

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

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

Ciò consente di separare le preoccupazioni tra la rappresentazione di base degli Articoli e il codice per il disegno. Il problema però è che le classi di oggetti dipendono dalle classi di disegno.

Come posso separare questo codice di disegno in una libreria separata? La soluzione per gli articoli è di restituire una classe di fabbrica con qualche descrizione? Tuttavia, come può essere definito in modo tale che la libreria Core non dipenda dalla libreria Draw?

Risposte:


3

Dai un'occhiata al modello di visitatore .

La sua intenzione è quella di separare un algoritmo (nel disegno del tuo caso) da una struttura di oggetti su cui opera (elementi).

Quindi un semplice esempio per il tuo problema sarebbe:

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;
}

Interessante: non avevo mai pensato di usare un visitatore con sovraccarico per ottenere il polimorfismo. Questo sovraccarico sarebbe corretto durante l'esecuzione?
the_mandrill,

Sì, si sovraccaricherebbe correttamente. Vedi risposta modificata
hotpotato

Vedo che funziona. È un peccato però che ogni classe derivata debba implementare il visit()metodo.
the_mandrill,

Questo mi ha spinto a fare un po 'più di ricerca e ora ho trovato l'implementazione del modello Visitatore di Loki che sembra essere esattamente quello che sto cercando! Conoscevo il modello di visitatore in termini di visite ai nodi sugli alberi, ma non ci avevo pensato in modo più astratto.
the_mandrill

2

La divisione che descrivi - in due gerarchie parallele - è essenzialmente il modello a ponte .

Temi che renda l' astrazione raffinata (Rectangle ecc.) Dipendente dall'implementazione (RectangleDrawer), ma non sono sicuro di come potresti evitarlo del tutto.

Puoi certamente fornire una factory astratta per interrompere quella dipendenza, ma hai ancora bisogno di un modo per dire alla factory quale sottoclasse creare e quella sottoclasse dipende ancora dalla specifica implementazione raffinata. Cioè, anche se Rectangle non dipende da RectangleDrawer, ha ancora bisogno di un modo per dire alla fabbrica di costruirne uno, e RectangleDrawer stesso dipende ancora da Rectangle.

Vale anche la pena chiedere se si tratta di un'astrazione utile. Disegnare un rettangolo è così difficile che vale la pena inserirlo in una classe diversa o stai davvero cercando di astrarre la libreria grafica?


Pensavo di aver visto quel modello prima di avere le gerarchie parallele - grazie per avermi mosso per questo. Voglio però astrarre la biblioteca grafica. Non voglio che la libreria principale debba dipendere dalla libreria grafica. Diciamo che l'applicazione principale gestisce gli oggetti forma e che la libreria per visualizzarli è un componente aggiuntivo. Ricorda però che questa non è un'applicazione per disegnare rettangoli: la sto solo usando come metafora.
the_mandrill,

Quello che sto pensando è che RectangleDrawer, CircleDrawerecc. , Potrebbe non essere il modo più utile per astrarre la libreria grafica. Se, invece, esponi un insieme di primitive attraverso una normale interfaccia astratta, interrompi comunque la dipendenza.
Inutile

1

Dai un'occhiata al polimorfismo basato sulla semantica del valore e sui concetti .

Questo lavoro ha cambiato il modo in cui guardo il polimorfismo. Abbiamo utilizzato questo sistema per influire notevolmente su situazioni ripetute.


Sembra affascinante - sembra che stia andando nella direzione che desidero
the_mandrill

1
Il collegamento è interrotto. Questo è il motivo per cui le risposte che sono essenzialmente solo link sono scoraggiate.
Aj

0

Il motivo per cui stai riscontrando un problema è perché gli oggetti non devono disegnare da soli. Disegnare qualcosa correttamente dipende da un miliardo di pezzi di stato e un rettangolo non avrà nessuno di quei pezzi. Dovresti avere un oggetto grafico centrale che rappresenti lo stato principale come backbuffer / buffer di profondità / shader / etc, e quindi assegnagli un metodo Draw ().


Concordo sul fatto che gli oggetti non dovrebbero attirare se stessi, quindi il desiderio di spostare quella capacità in una classe diversa per ottenere una separazione delle preoccupazioni. Supponiamo che queste classi fossero Dogo Cat- l'oggetto responsabile per disegnarle sia molto più complesso che per disegnare un rettangolo.
the_mandrill
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.