2010-07-12 5 views
2

J'essaie de trouver une solution propre à un problème de questionnaire. Disons que j'ai une classe Questionnaire qui a une collection de Answer s, par ex.Questions avec différents types de réponses dans NHibernate

public class Questionnaire 
{ 
    public virtual ISet<Answer> Answers {get;set;} 
} 

Les réponses doivent être de différents types en fonction de la question, par ex. date de naissance, marques sur dix, pourquoi pensez-vous, etc.

Ma première pensée était quelque chose comme ceci:

public class Question 
{ 
    public virtual QuestionType TypeOfQuestion {get;set;} 
    public virtual string PromptText {get;set;} 
} 

public class Answer 
{ 
    public virtual Question Question {get;set;} 
} 

public class DateTimeAnswer : Answer 
{ 
    public virtual DateTime Response {get;set;} 
}   

public class IntegerAnswer : Answer 
{ 
    public virtual int Response {get;set;} 
}   
// etc. 

Le problème évident serait que du questionnaire, il n'y a pas accès à la Response propriété:

questionnaire.Answers[0].Response; // compile error 

serait de même à une interface. Il serait plus agréable d'utiliser une interface générique, par exemple:

public interface IAnswer<T> 
{ 
    public virtual Question Question {get;set;} 
    public virtual T Response {get;set;} 
} 

public class DateTimeAnswer : IAnswer<DateTime> {} 

Le problème vient alors dans la classe Questionnaire, comme le type de IAnswer doit fournir:

public class Questionnaire 
{ 
    public virtual ISet<IAnswer<???>> Answers {get;set;} 
} 

Il est clair que je ne vouloir avoir de nombreuses collections de IAnswer chacun avec différents types. Je pourrais utiliser

ISet<IAnswer<dynamic>> 

mais NHibernate ne l'aimerait pas. Je réalise qu'un compromis est nécessaire quelque part, mais je ne suis pas sûr de savoir lequel est le plus joli. Qu'est-ce que tu ferais?

Répondre

4

Je pense que la sous-classe Question ainsi que la réponse est un bon plan - se débarrasser de cette énumération QuestionType.

Vous pouvez alors avoir une méthode abstraite MakeAnswer (string) sur Question qui encapsule beaucoup de logique pour vous, et peut faire une conversion de type etc si nécessaire.

Ma solution au problème générique sur Rob's answer serait quelque chose comme ceci:

public interface IAnswer 
{ 
    Question Question { get; } 
    void Respond(IAnswerFormatter formatter); 
} 

IAnswerFormatter ressemble à quelque chose comme ceci:

public interface IAnswerFormatter 
{ 
    void Format(string value); 
    void Format(DateTime value); 
    ... 
} 

Et DateTimeAnswer ressemblerait à ceci:

public class DateTimeAnswer : IAnswer 
{ 
    public DateTimeAnswer(Question question, DateTime value) 
    { 
     Question = question; 
    } 
    public Question Question { get; protected set; } 

    protected DateTime Response {get; set;} 

    public virtual void Respond(IAnswerFormatter formatter) 
    { 
     formatter.Write(Response); 
    } 
} 

Et DateTimeQuestion pourrait ressembler à:

public class DateTimeQuestion : Question 
{ 
    public IAnswer MakeAnswer(string value) 
    { 
     // Ignoring error handling here 
     return new DateTimeAnswer(this, DateTime.Parse(value)); 
    } 
} 

De cette façon, votre réponse peut contenir la valeur à leur manière, et vous mappings NH pouvez spécifier la façon dont il regarde dans la base de données (je vous recommande la cartographie à l'aide discriminateurs) votre question peut être responsable de la création des réponses de réponses génériques.

1

Problème intéressant ..

Mes commentaires/pensées:

  • Comme Steve said - se débarrasser de ce QuestionType méchant ENUM!
  • Retirez le ISet<T> - Je ne pense pas que cela ajoute une valeur ..

Je penserais le long des lignes de quelque chose comme:

public class Questionnaire 
{ 
public AnswerCollection Answers { get; set; } 
} 

public class AnswerCollection : Collection<Answer> 
{ 
    // Subclassed Collection<T> for Add/Remove Semantics etc. 
} 

public abstract class Answer : IAnswer<object> 
{ 
    public override object Response { get { // Base Impl. Here }; } 

    public abstract string CommonOperation() 
{ 
    // This is the key, the "common operation" - likely ToString? 
    // (for rendering the answer to the screen) 
    // Hollywood Principle - let the answers figure out how they 
    // are to be displayed... 
} 
} 

public class DateTimeAnswer : Answer, IAnswer<DateTime> 
{ 
public override DateTime Response { get { // Do Stuff }; } 
public override string CommonOperation() { return "I can haz DateTime"; } 
} 

L'idée étant ici, nous devons aller à l'essence de ce que vous faites à TOUS les objets, ce qui est susceptible d'afficher simplement la réponse .. Nous ajoutons la sécurité de type au moyen de génériques de sorte que nous pouvons être sûrs que nous ne pouvons pas créer de nouvelles réponses sans un type paramètre ..

Nous pouvons alors être sûrs que ce qui entre et sort est limité aux types de réponses que nous implémentons. NHib ne devrait avoir aucun problème réel traitant de ceci puisqu'il sait ce dont il a besoin.

Bien qu'il suce, nous avons la « object » version du Answer en revenant de la collection, qui est la que la collection est, Réponses.

Est-ce que cela aide? :)

+1

Je suis d'accord sur la structure générale, mais je pense que vous avez exposé un problème avec IAnswer en tant que classe générique. Dans votre exemple, DateTimeAnswer n'est pas une réponse et ne peut donc pas aller dans un AnswersCollection. Vous devrez également hériter de Answer, et implémenter la version de l'objet de la propriété Response ainsi que votre version fortement typée. A partir de la question, vous ne répéteriez jamais les réponses que la réponse DateTime fortement typée semble être une perte. – spmason

+0

Cela aide, merci. Je vais y réfléchir et l'essayer. – harriyott

+0

Ah, Steve - bon endroit, la réponse devrait sous-classe Répondre et implémenter l'interface. Je vais corriger. La typage fort est plus pour la tranquillité d'esprit sur la création, vous ne pouvez pas créer la version "objet" car elle est abstraite .. C'est le problème classique de "aimer le typage fort, mais en fait seulement * vraiment * en avoir besoin 10% du temps ".. –

-1

Est-il vraiment logique de stocker les réponses dans un modèle de données complet? Qu'allez-vous faire avec eux? Le candidat le plus probable semble signaler le cas dans lequel vous voudrez peut-être examiner le style CQRS (Command Query Responsibility Separation). Vous auriez à la place un QuestionnaireCompletedCommand qui contiendrait une liste de réponses que vous persisteriez alors de manière à ce qu'un rapport puisse être exécuté contre eux.

Les modèles de données sont parfaits lorsque vous avez une logique métier (ce qui est possible), mais si vous n'avez pas de logique applicative, vous risquez de compliquer inutilement la solution. En parlant de compliquer quand je dis regarder CQRS je ne veux pas nécessairement dire la partie de l'événement. C'est une énorme complication dont très peu de gens ont besoin.

Questions connexes