Problème avec le design pattern visitor
Bonjour,
Je suis en train de coder un petit moteur 2D, et, étant débutant, je me heurte à de nombreux problèmes de conceptions dont un que je n'arrive pas à résoudre seul. En effet, j'aimerais mettre en œuvre un système de gestion de collisions capable de faire interagir différents types de collisions entres eux (Box, Point, Circle...).
Afin de rendre opaque les différents algorithmes d'interaction des collisions et de rendre générique leur utilisation, j'étais parti sur l'idée de créer une classe abstraite ACollision, de laquelle je ferais dériver une classe fille par type de collision à gérer. Voici un exemple édulcoré de code pour donner une idée plus précise de ce que j'imagine :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| class ACollision
{
public :
virtual ~ACollision( void ) {};
virtual void check_collision( const Box& box ) = 0;
virtual void check_collision( const Circle& circle ) = 0;
virtual void check_collision( const Point& point ) = 0;
};
class Box : public ACollision
{
public :
virtual void check_collision( const Box& box )
{
std::cout << "Box/Box collision" << std::endl;
}
virtual void check_collision( const Circle& circle )
{
std::cout << "Box/Circle collision" << std::endl;
}
virtual void check_collision( const Point& point )
{
std::cout << "Box/Point collision" << std::endl;
}
};
class Circle : public ACollision
{
public :
virtual void check_collision( const Box& box )
{
std::cout << "Circle/Box collision" << std::endl;
}
virtual void check_collision( const Circle& circle )
{
std::cout << "Circle/Circle collison" << std::endl;
}
virtual void check_collision( const Point& point )
{
std::cout << "Circle/Point collision" << std::endl;
}
};
class Point : public ACollision
{
public :
virtual void check_collision( const Box& box )
{
std::cout << "Point/Box collision" << std::endl;
}
virtual void check_collision( const Circle& circle )
{
std::cout << "Point/Circle collison" << std::endl;
}
virtual void check_collision( const Point& point )
{
std::cout << "Point/Point collision" << std::endl;
}
}; |
J'aurais donc voulu gérer mes collisions par polymorphisme et laisser les différentes surcharges de la fonction check_collision s'occuper d'utiliser le bon algorithme pour moi. Seulement voilà, sur un pointeur ACollision, impossible d’appeler les méthodes check_collision puisqu'elles ne sont pas encore connues. J'aurais pu faire des dynamic_cast pour résoudre le problème, mais j'ai lu partout que c'était à éviter car vraiment pas propre.
J'ai donc demandé conseil sur le chat du site, et on m'a orienté vers le design pattern visitor, qui semble en effet tout indiqué dans mon cas. Seulement voilà, si je l'implémente dans sa forme classique, je ne résous pas vraiment mon problème. En effet, les implémentations que j'ai pu voir du pattern visitor permettent bien de déclencher une action spécifique à un type de classe donné, mais pas de faire interagir deux classes entre elles. Voilà ce que j'ai tenté :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
| #include <iostream>
#include <list>
class Visitor;
class Box;
class Circle;
class Point;
class Visitor
{
public:
virtual void visit( Box& box ) = 0;
virtual void visit( Circle& circle ) = 0;
virtual void visit( Point& point ) = 0;
};
class ACollision
{
public :
virtual ~ACollision( void ) {};
virtual void accept( Visitor& visitor ) = 0;
};
class Box : public ACollision
{
public :
virtual void accept( Visitor& visitor )
{
return ( visitor.visit( *this ) );
}
virtual void check_collision( const Box& box )
{
std::cout << "Box/Box collision" << std::endl;
}
virtual void check_collision( const Circle& circle )
{
std::cout << "Box/Circle collision" << std::endl;
}
virtual void check_collision( const Point& point )
{
std::cout << "Box/Point collision" << std::endl;
}
};
class Circle : public ACollision
{
public :
virtual void accept( Visitor& visitor )
{
return ( visitor.visit( *this ) );
}
virtual void check_collision( const Box& box )
{
std::cout << "Circle/Box collision" << std::endl;
}
virtual void check_collision( const Circle& circle )
{
std::cout << "Circle/Circle collison" << std::endl;
}
virtual void check_collision( const Point& point )
{
std::cout << "Circle/Point collision" << std::endl;
}
};
class Point : public ACollision
{
public :
virtual void accept( Visitor& visitor )
{
return ( visitor.visit( *this ) );
}
virtual void check_collision( const Box& box )
{
std::cout << "Point/Box collision" << std::endl;
}
virtual void check_collision( const Circle& circle )
{
std::cout << "Point/Circle collison" << std::endl;
}
virtual void check_collision( const Point& point )
{
std::cout << "Point/Point collision" << std::endl;
}
};
class Collision_visitor : public Visitor
{
public :
Collision_visitor(const ACollision& object) :
object( object )
{
}
virtual void visit( Box& box )
{
object.check_collision( box ); //Problème : l'une des deux classes à comparer n'est pas de type concret, je n'ai donc pas moyen de faire appeler la bonne méthode check_collision!
}
virtual void visit( Circle& circle )
{
object.check_collision( circle ); //Problème : l'une des deux classes à comparer n'est pas de type concret, je n'ai donc pas moyen de faire appeler la bonne méthode check_collision!
}
virtual void visit( Point& point )
{
object.check_collision( point ); //Problème : l'une des deux classes à comparer n'est pas de type concret, je n'ai donc pas moyen de faire appeler la bonne méthode check_collision!
}
private :
const ACollision& object;
};
int main( void )
{
std::list< ACollision* > objects_list;
objects_list.push_back( new ( Box ) );
objects_list.push_back( new ( Circle ) );
objects_list.push_back( new ( Point ) );
std::list< ACollision* >::iterator objects_it1 = objects_list.begin();
std::list< ACollision* >::iterator objects_end = objects_list.end();
for ( ; objects_it1 != objects_end ; ++objects_it1)
{
std::list< ACollision* >::iterator objects_it2 = objects_list.begin();
for ( ; objects_it2 != objects_end ; ++objects_it2 )
{
Collision_visitor collision_visitor( *( *objects_it2 ) );
( *objects_it1 )->accept( collision_visitor );
}
}
return ( 0 );
} |
J'ai beau retourner le problème dans tous les sens, j'ai toujours l'une des deux classes dont le type m'est inconnu, ce qui m'empêche de pouvoir appeller la bonne méthode. Sur l'exemple ci dessus, dans la classe Collision_visitor, je me retrouve avec une référence sur un ACollision, et une classe fille dont le type est donc connu. Seulement, impossible de faire appeler la bonne méthode check_collision via la référence sur ACollision, car cette dernière ne connais pas encore cette méthode. A l'inverse, impossible d'envoyer la classe ACollision à la classe de type connu, car les méthodes check_collisions sont censée traiter un type précis, hors ACollision n'est pas une collision traitable en tant que telle.
J'ai tenté de trafiquer le code précédent, et j'ai trouvé une solution qui fonctionne parfaitement. Seulement voilà, pour résoudre mon problème, j'ai dû déclarer des méthodes dans la classe mère qui reçoivent des types de classe fille. Sans être un crack en conception, il me semble que le fait d'avoir une classe mère au courant des classes filles est relativement crado. Voilà ce que ça donne :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| #include <iostream>
#include <list>
class Box;
class Circle;
class Point;
class ACollision
{
public :
virtual ~ACollision( void ) {};
virtual void check_collision( ACollision& object ) = 0;
virtual void check_collision( const Box& box ) = 0;
virtual void check_collision( const Circle& circle ) = 0;
virtual void check_collision( const Point& point ) = 0;
};
class Box : public ACollision
{
public :
virtual void check_collision( ACollision& object )
{
return ( object.check_collision( *this ) );
}
virtual void check_collision( const Box& box )
{
std::cout << "Box/Box collision" << std::endl;
}
virtual void check_collision( const Circle& circle )
{
std::cout << "Box/Circle collision" << std::endl;
}
virtual void check_collision( const Point& point )
{
std::cout << "Box/Point collision" << std::endl;
}
};
class Circle : public ACollision
{
public :
virtual void check_collision( ACollision& object )
{
return ( object.check_collision( *this ) );
}
virtual void check_collision( const Box& box )
{
std::cout << "Circle/Box collision" << std::endl;
}
virtual void check_collision( const Circle& circle )
{
std::cout << "Circle/Circle collison" << std::endl;
}
virtual void check_collision( const Point& point )
{
std::cout << "Circle/Point collision" << std::endl;
}
};
class Point : public ACollision
{
public :
virtual void check_collision( ACollision& object )
{
return ( object.check_collision( *this ) );
}
virtual void check_collision( const Box& box )
{
std::cout << "Point/Box collision" << std::endl;
}
virtual void check_collision( const Circle& circle )
{
std::cout << "Point/Circle collison" << std::endl;
}
virtual void check_collision( const Point& point )
{
std::cout << "Point/Point collision" << std::endl;
}
};
int main( void )
{
std::list< ACollision* > objects_list;
objects_list.push_back( new ( Box ) );
objects_list.push_back( new ( Circle ) );
objects_list.push_back( new ( Point ) );
std::list< ACollision* >::iterator objects_it1 = objects_list.begin();
std::list< ACollision* >::iterator objects_end = objects_list.end();
for ( ; objects_it1 != objects_end ; ++objects_it1)
{
std::list< ACollision* >::iterator objects_it2 = objects_list.begin();
for ( ; objects_it2 != objects_end ; ++objects_it2 )
( *objects_it1 )->check_collision( *( *objects_it2 ) );
}
return ( 0 );
} |
Ce code affiche :
Citation:
Box/Box collision
Circle/Box collision
Point/Box collision
Box/Circle collision
Circle/Circle collison
Point/Circle collison
Box/Point collision
Circle/Point collision
Point/Point collision
Ce qui est tout à fait valide. C'est exactement le comportement que je recherchais.
J'en arrive donc à ma question : Comment pourrais-je implémenter ce mécanisme de manière propre?
En vous remerciant d'avance.