2010-07-12 53 views
2

我正在尝试找到问卷调查问题的整齐解决方案。让我们说,我有一个Questionnaire类,它有一个集合Answer,例如在NHibernate中提供不同类型的问题的答案

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 property:

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不会喜欢它。

我意识到需要妥协某处,但我不确定哪个是最漂亮的。你会怎么做?

回答

4

我认为子类问题以及答案是一个很好的计划 - 摆脱QuestionType枚举。

然后,您可以在Question上为MakeAnswer(string)抽象方法,它为您封装了大量逻辑,并且可以根据需要进行类型转换等。

我对通用的问题的解决上Rob's answer将是这样的:

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

有趣的问题..

我的意见/想法:

  • 作为Steve said - 摆脱讨厌的QuestionType枚举的!
  • 取出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"; } 
} 

的想法是在这里,我们需要深入了解你正在做的事情的实质所有对象的,这可能只是显示答案。我们通过泛型添加类型安全性,所以我们可以确定,如果没有类型,我们不能创建新的响应参数..

然后,我们可以确定进出的内容仅限于我们实现的答案类型。 NHib应该没有真正的问题处理,因为它知道它需要什么。

虽然很烂,我们有Answer来从收集回来的“object”版本,即的集合是什么,答案

这是否帮助? :)

+1

我同意一般结构,但我认为你已经暴露了将IAnswer作为泛型类的问题。 在您的示例DateTimeAnswer不是答案,因此无法进入AnswersCollection。你必须让它继承自Answer,并且实现Response属性的对象版本以及你的强类型对象。 从问题你只会遍历答案强类型DateTime响应似乎是一种浪费。 – spmason 2010-07-13 08:25:46

+0

它确实有帮助,谢谢。我会考虑一下并尝试一下。 – harriyott 2010-07-13 08:28:37

+0

啊,史蒂夫 - 好点,答案应该子类答案和实现接口。我会纠正的。强大的打字更多的是为了创作的和平,你不能创建“对象”版本,因为它是抽象的。这是一个经典的问题,“想要强大的打字,但实际上只有*真的需要它10%的时间“.. – 2010-07-13 08:42:02

-1

是否真的有意义将数据模型存储在完整的答案中?你将与​​他们一起做什么?

最有可能的候选人似乎是报告在这种情况下,您可能希望查看CQRS(命令查询责任分离)样式。你将会得到一个QuestionnaireCompletedCommand,它将包含一个答案清单,然后你会以某种方式坚持这样的答案,以使报告可能会遭遇。

当您有业务逻辑时(您可能会),数据模型非常棒,但如果您没有任何业务逻辑,您可能会不必要地使解决方案复杂化。说到看到CQRS时,我并不一定指事件采购部分。这是极少数人需要的巨大并发症。