Modèle de conception pour un comportement polymorphe tout en permettant la séparation des bibliothèques

10

Disons que j'ai une hiérarchie de Itemcours: Rectangle, Circle, Triangle. Je veux pouvoir les dessiner, donc ma première possibilité est d'ajouter une Draw()méthode virtuelle à chacun:

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

Cependant, je souhaite diviser la fonctionnalité de dessin en une bibliothèque Draw distincte tandis que la bibliothèque Core ne contient que les représentations de base. Il y a quelques possibilités auxquelles je peux penser:

1 - A DrawManagerqui prend une liste de Items et doit utiliser dynamic_cast<>pour déterminer quoi faire:

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

Ce n'est pas idéal car il repose sur RTTI et force une classe à être au courant de tous les éléments de la hiérarchie.

2 - L'autre approche consiste à reporter l'attribution de la responsabilité à une ItemDrawerhiérarchie ( RectangleDrawer, etc.):

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

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

Cela permet de séparer les préoccupations entre la représentation de base des articles et le code de dessin. Le problème est que les classes d'objets dépendent des classes de dessin.

Comment puis-je séparer ce code de dessin dans une bibliothèque distincte? La solution consiste-t-elle pour les articles à renvoyer une classe d'usine d'une certaine description? Cependant, comment cela peut-il être défini afin que la bibliothèque Core ne dépende pas de la bibliothèque Draw?

the_mandrill
la source

Réponses:

3

Jetez un oeil au modèle de visiteur .

Son intention est de séparer un algorithme (dans votre dessin de cas) d'une structure d'objet sur laquelle il opère (items).

Un exemple simple de votre problème serait donc:

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;
}
patate chaude
la source
Intéressant - je n'avais jamais pensé à utiliser un visiteur surchargé pour réaliser le polymorphisme. Cela surchargerait-il correctement au moment de l'exécution?
the_mandrill
Oui, cela surchargerait correctement. Voir la réponse modifiée
hotpotato
Je peux voir que cela fonctionne. C'est dommage cependant que chaque classe dérivée doive implémenter la visit()méthode.
the_mandrill
Cela m'a incité à faire un peu plus de recherche et j'ai maintenant trouvé l'implémentation du modèle de visiteur de Loki qui semble être exactement ce que je recherche! Je connaissais le schéma des visiteurs en termes de visites de nœuds dans les arbres, mais je n'y avais pas pensé de manière plus abstraite.
the_mandrill
2

La division que vous décrivez - en deux hiérarchies parallèles - est essentiellement le modèle de pont .

Vous craignez que cela rend l' abstraction raffinée (Rectangle, etc.) dépendante de l' implémentation (RectangleDrawer), mais je ne sais pas comment vous pourriez l'éviter complètement.

Vous pouvez certainement fournir une fabrique abstraite pour briser cette dépendance, mais vous avez toujours besoin d'un moyen de dire à la fabrique quelle sous-classe à créer, et cette sous-classe dépend toujours de l'implémentation affinée spécifique. Autrement dit, même si Rectangle ne dépend pas de RectangleDrawer, il a toujours besoin d'un moyen de dire à l'usine d'en créer un, et RectangleDrawer lui-même dépend toujours de Rectangle.

Il convient également de se demander s'il s'agit d'une abstraction utile. Le dessin d'un rectangle est-il si difficile qu'il vaut la peine de le mettre dans une classe différente, ou essayez-vous vraiment d'abstraire la bibliothèque graphique?

Inutile
la source
Je pensais avoir vu ce schéma avant d'avoir les hiérarchies parallèles - merci de m'avoir donné un coup de tête. Je veux cependant faire abstraction de la bibliothèque graphique. Je ne veux pas que la bibliothèque principale doive dépendre de la bibliothèque graphique. Disons que l'application principale gère les objets de forme et que la bibliothèque pour les afficher est un module complémentaire. Gardez à l'esprit que ce n'est vraiment pas une application pour dessiner des rectangles - je l'utilise juste comme métaphore.
the_mandrill
Ce que je pense, c'est que RectangleDrawer, CircleDraweretc. n'est peut-être pas le moyen le plus utile d'abstraire la bibliothèque graphique. Si, au lieu de cela, vous exposez un ensemble de primitives via une interface abstraite régulière, vous rompez toujours la dépendance.
Inutile
1

Jetez un œil à la sémantique des valeurs et au polymorphisme basé sur les concepts .

Ce travail a changé ma façon de voir le polymorphisme. Nous avons utilisé ce système avec beaucoup d'affect dans des situations répétées.

Bill Door
la source
Cela a l'air fascinant - il semble qu'il se dirige dans la direction que je veux
the_mandrill
1
Le lien est rompu. C'est pourquoi les réponses qui sont essentiellement liées uniquement à des liens sont déconseillées.
ajb
0

La raison pour laquelle vous rencontrez un problème est que les objets ne doivent pas se dessiner. Dessiner quelque chose correctement dépend d'un milliard de pièces d'état et un rectangle ne comportera aucune de ces pièces. Vous devriez avoir un objet graphique central qui représente l'état de base comme backbuffer / depth buffer / shaders / etc, puis lui donner une méthode Draw ().

DeadMG
la source
Je suis d'accord que les objets ne devraient pas se dessiner, d'où le désir de déplacer cette capacité dans une classe différente pour réaliser une séparation des préoccupations. Supposons que ces classes soient Dogou Cat- l'objet chargé de les dessiner est beaucoup plus complexe que de dessiner un rectangle.
the_mandrill