Disons que j'ai un générateur de tests, pour que les enseignants puissent créer un tas de questions pour un test.
Cependant, toutes les questions ne sont pas identiques: vous avez plusieurs choix, zone de texte, correspondance, etc. Chacun de ces types de questions doit stocker différents types de données et nécessite une interface graphique différente pour le créateur et pour le candidat.
Je voudrais éviter deux choses:
- Contrôles de type ou transtypage
- Tout ce qui concerne l'interface graphique dans mon code de données.
Dans ma première tentative, je me retrouve avec les classes suivantes:
class Test{
List<Question> questions;
}
interface Question { }
class MultipleChoice implements Question {}
class TextBox implements Question {}
Cependant, lorsque je vais afficher le test, je me retrouverais inévitablement avec du code comme:
for (Question question: questions){
if (question instanceof MultipleChoice){
display.add(new MultipleChoiceViewer());
}
//etc
}
Cela ressemble à un problème très courant. Y a-t-il un modèle de conception qui me permet d'avoir des questions polymorphes tout en évitant les éléments énumérés ci-dessus? Ou le polymorphisme est-il une mauvaise idée en premier lieu?
la source
Réponses:
Vous pouvez utiliser un modèle de visiteur:
Une autre option est une union discriminée. Cela dépendra beaucoup de votre langue. C'est beaucoup mieux si votre langue le prend en charge, mais pas beaucoup de langues populaires.
la source
visit
(le visiteur visite). De plus, la méthode dans les objets visités est généralement appeléeaccept(Visitor)
(l'objet accepte un visiteur). Voir oodesign.com/visitor-pattern.htmlEn C # / WPF (et, j'imagine, dans d'autres langages de conception axés sur l'interface utilisateur), nous avons des DataTemplates . En définissant des modèles de données, vous créez une association entre un type «d'objet de données» et un «modèle d'interface utilisateur» spécialisé créé spécifiquement pour afficher cet objet.
Une fois que vous avez fourni des instructions pour que l'interface utilisateur charge un type spécifique d'objet, il verra s'il existe des modèles de données définis pour l'objet.
la source
Si chaque réponse peut être codée sous forme de chaîne, vous pouvez le faire:
Où la chaîne vide signifie une question sans réponse pour le moment. Cela permet de séparer les questions, les réponses et l'interface graphique tout en permettant le polymorphisme.
La zone de texte, la correspondance, etc. pourraient avoir des conceptions similaires, implémentant toutes l'interface de question. La construction de la chaîne de réponse se produit dans la vue. Les chaînes de réponse représentent l'état du test. Ils doivent être conservés au fur et à mesure que l'élève progresse. Les appliquer aux questions permet d'afficher le test et son état à la fois gradué et non gradué.
En séparant la sortie
display()
etdisplayGraded()
la vue n'a pas besoin d'être échangée et aucune ramification ne doit être effectuée sur les paramètres. Cependant, chaque vue est libre de réutiliser autant de logique d'affichage que possible lors de l'affichage. Quel que soit le schéma conçu pour le faire, il n'a pas besoin de fuir dans ce code.Si, cependant, vous souhaitez avoir un contrôle plus dynamique de l'affichage d'une question, vous pouvez le faire:
et ça
Cela présente l'inconvénient de nécessiter des vues qui n'ont pas l'intention d'afficher
score()
ouanswerKey
de dépendre d'eux lorsqu'elles n'en ont pas besoin. Mais cela signifie que vous n'avez pas à reconstruire les questions de test pour chaque type de vue que vous souhaitez utiliser.la source
À mon avis, si vous avez besoin d'une telle fonctionnalité générique, je diminuerais le couplage entre les éléments du code. J'essaierais de définir le type de question le plus générique possible, puis je créerais différentes classes pour les objets de rendu. Veuillez consulter les exemples ci-dessous:
Ensuite, pour la partie de rendu, j'ai supprimé la vérification de type en implémentant une simple vérification des données dans l'objet question. Le code ci-dessous essaie d'accomplir deux choses: (i) éviter la vérification de type et éviter la violation du principe "L" (substitution Liskov dans SOLID) en supprimant le sous-typage de la classe Question; et (ii) rendre le code extensible, en ne modifiant jamais le code de rendu principal ci-dessous, en ajoutant simplement plus d'implémentations QuestionView et ses instances au tableau (c'est en fait le principe "O" dans SOLID - ouvert pour extension et fermé pour modification).
la source
Une usine devrait pouvoir le faire. La carte remplace l'instruction switch, qui est uniquement nécessaire pour associer la question (qui ne sait rien de la vue) avec la questionView.
Avec cela, la vue utilise le type spécifique de question qu'elle est capable d'afficher, et le modèle reste déconnecté de la vue.
L'usine peut être remplie par réflexion ou manuellement au démarrage de l'application.
la source
Question
dans unMultipleChoiceQuestion
lorsque vous créez leMultipleChoiceView
Je ne suis pas sûr que cela compte comme "éviter les vérifications de type", selon ce que vous pensez de la réflexion .
la source
if
vérification de type à unedictionary
vérification de type. Comme la façon dont Python utilise les dictionnaires au lieu des instructions switch. Cela dit, j'aime cette façon plus d'une liste des cas relevés.template <typename Q> struct question_traits;
avec les spécialisations appropriées