Вопросы с разными типами ответов в NHibernate - PullRequest
2 голосов
/ 13 июля 2010

Я пытаюсь найти аккуратное решение проблемы анкеты. Допустим, у меня есть класс Questionnaire, который имеет коллекцию Answer с, например,

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

Ответы должны быть разных типов в зависимости от вопроса, например, дата рождения, отметка из десяти, почему вы думаете и т. д.

Моя первая мысль была примерно такой:

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.

Очевидная проблема заключается в том, что из вопросника нет доступа к свойству Response:

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

То же самое относится к интерфейсу. Было бы лучше использовать универсальный интерфейс, такой как:

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

public class DateTimeAnswer : IAnswer<DateTime> {}

В этом случае проблема возникает в классе Questionnaire, поскольку необходимо указать тип IAnswer:

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

Очевидно, я не хочу иметь много коллекций IAnswer, каждый с разными типами. Я мог бы использовать

ISet<IAnswer<dynamic>> 

но тогда NHibernate не понравится.

Я понимаю, что где-то нужен компромисс, но я не уверен, какой из них самый красивый. Что бы вы сделали?

Ответы [ 3 ]

4 голосов
/ 13 июля 2010

Я думаю, что подклассификация Вопроса, а также ответа - хороший план - избавьтесь от этого перечисления QuestionType.

Затем вы можете иметь абстрактный метод MakeAnswer (string) для Question, который инкапсулирует много логики для вас.и может выполнять преобразование типов и т.д., если это необходимо.

Мое решение общей проблемы на Ответ Роба будет выглядеть примерно так:

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

Где IAnswerFormatterвыглядит примерно так:

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

И DateTimeAnswer будет выглядеть так:

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

И DateTimeQuestion может выглядеть так:

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

Таким образом, ваш ответ можетхраните значение по-своему, и вы, сопоставления NH, можете указать, как оно выглядит в базе данных (я рекомендовал бы сопоставление с использованием дискриминаторов), ваш вопрос может отвечать за создание ответов из общих ответов.

1 голос
/ 13 июля 2010

Интересная проблема ..

Мои комментарии / мысли:

  • Как Стив сказал - избавься от этого неприятного QuestionType enum!
  • Удалите ISet<T> - я не думаю, что это добавляет какую-либо ценность ..

Я бы подумал что-то вроде:

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

Идея заключается в том, что нам нужно понять суть того, что вы делаете с ALL объектов, что, вероятно, просто отображает ответ. Мы добавляем безопасность типов посредством обобщений, так мы можем быть уверены, что не сможем создавать новые ответы без параметра типа ..

Тогда мы можем быть почти уверены, что то, что входит и выходит, ограничивается типами ответов, которые мы реализуем. У NHib не должно быть проблем с этим, поскольку он знает, что ему нужно.

Хотя это отстой, у нас есть "object" версия Answer, возвращаемая из коллекции, что является значением коллекции, Ответы .

Это помогает? :)

0 голосов
/ 13 июля 2010

Имеет ли смысл хранить ответы в полной мере на модели данных? Что ты будешь делать с ними?

Наиболее вероятный кандидат, по-видимому, сообщает, в этом случае вы можете захотеть взглянуть на стиль CQRS (разделение ответственности при выполнении команд). Вместо этого у вас будет QuestionnaireCompletedCommand, который будет содержать список ответов, которые вы затем сохраните в некотором роде, чтобы отчеты могли быть обработаны для них.

Модели данных хороши, когда у вас есть бизнес-логика (которая у вас может быть), но если у вас нет бизнес-логики, вы, вероятно, просто излишне усложняете решение. Говоря о усложнении, когда я говорю, посмотрите на CQRS, я не обязательно имею в виду источник событий. Это огромное осложнение, в котором мало кто нуждается.

...