.NET类设计问题

我有一个名为Question的类,它有一个名为Type的属性。 基于这种类型,我想以特定的方式将问题呈现给html(多选=单选按钮,多个答案=复选框等…)。 我开始使用单个RenderHtml方法,根据问题类型调用子方法,但我正在考虑将渲染逻辑分离为实现接口的各个类可能更好。 但是,由于此类使用NHibernate持久化到数据库,并且接口实现依赖于属性,因此我不确定如何最好地布局类。

有问题的课程:

public class Question { public Guid ID { get; set; } public int Number { get; set; } public QuestionType Type { get; set; } public string Content { get; set; } public Section Section { get; set; } public IList Answers { get; set; } } 

基于QuestionType枚举属性,我想呈现以下内容(只是一个示例):

 
[Content]
[Answer Value] [Answer Value] [Answer Value] ...

目前,我在一个名为RenderHtml()的函数中有一个大的switch语句,可以完成脏工作,但是我想把它转移到更干净的东西上。 我只是不确定如何。

有什么想法吗?

编辑:感谢大家的答案!

我最终使用以下界面使用策略模式:

 public interface IQuestionRenderer { string RenderHtml(Question question); } 

以下实施:

 public class MultipleChoiceQuestionRenderer : IQuestionRenderer { #region IQuestionRenderer Members public string RenderHtml(Question question) { var wrapper = new HtmlGenericControl("div"); wrapper.ID = question.ID.ToString(); wrapper.Attributes.Add("class", "question-wrapper"); var content = new HtmlGenericControl("div"); content.Attributes.Add("class", "question-content"); content.InnerHtml = question.Content; wrapper.Controls.Add(content); var answers = new HtmlGenericControl("div"); answers.Attributes.Add("class", "question-answers"); wrapper.Controls.Add(answers); foreach (var answer in question.Answers) { var answerLabel = new HtmlGenericControl("label"); answerLabel.Attributes.Add("for", answer.ID.ToString()); answers.Controls.Add(answerLabel); var answerTag = new HtmlInputRadioButton(); answerTag.ID = answer.ID.ToString(); answerTag.Name = question.ID.ToString(); answer.Value = answer.ID.ToString(); answerLabel.Controls.Add(answerTag); var answerValue = new HtmlGenericControl(); answerValue.InnerHtml = answer.Value + "
"; answerLabel.Controls.Add(answerValue); } var stringWriter = new StringWriter(); var htmlWriter = new HtmlTextWriter(stringWriter); wrapper.RenderControl(htmlWriter); return stringWriter.ToString(); } #endregion }

修改后的Question类使用内部字典,如下所示:

 public class Question { private Dictionary _renderers = new Dictionary { { QuestionType.MultipleChoice, new MultipleChoiceQuestionRenderer() } }; public Guid ID { get; set; } public int Number { get; set; } public QuestionType Type { get; set; } public string Content { get; set; } public Section Section { get; set; } public IList Answers { get; set; } public string RenderHtml() { var renderer = _renderers[Type]; return renderer.RenderHtml(this); } } 

看起来很干净。 🙂

例如,您可以使用策略模式 :

  1. 让所有HTML渲染器都实现一个公共接口,例如IQuestionRenderer ,方法名称为Render(Question)

  2. 在您的应用程序中有一个Dictionary的实例。 在初始化时填充它,可能基于配置文件。

  3. 对于问题的给定实例,请执行: renderers[question.Type].Render(question)

或者,您可以使用名为RenderXXX方法,其中XXX是问题类型,并使用reflection调用它们。

一般来说,每当你看到Type或Enum上的开关时,就意味着你可以将对象替换为“Type” – 换句话说,就是多态的情况。

这实际上意味着你将为每个Question类型创建一个不同的类并覆盖RenderHTML()函数。 每个Question对象都有责任知道它应该输出什么输入类型。

好处是您删除switch语句以及生成良好的基于​​OO的代码。 缺点是你为每个问题类型添加一个类(在这种情况下影响最小)。

这是使用对象inheritance来实现所需内容的经典案例。 每当你看到一个大的switch语句切换对象的类型时,你应该考虑某种forms的子类。

我看到两种方法,取决于这些问题类型的“常见”方式,以及渲染是否是它们之间的唯一区别:

选项1 – 对问题类进行子类化

 public class Question { public Guid ID { get; set; } public int Number { get; set; } public string Content { get; set; } public Section Section { get; set; } public IList Answers { get; set; } public virtual string RenderHtml(); } public class MultipleChoiceQuestion { public string RenderHtml() { // render a radio button } } public class MultipleAnswerQuestion { public string RenderHtml() { // render a radio button } } 

选项2 – 创建渲染界面,并将其作为问题类的属性

 public class Question { public Guid ID { get; set; } public int Number { get; set; } public string Content { get; set; } public Section Section { get; set; } public IList Answers { get; set; } public IRenderer Renderer { get; private set; } } public interface IRenderer { void RenderHtml(Question q); } public class MultipleChoiceRenderer : IRenderer { public string RenderHtml(Question q) { // render a radio button } } public class MultipleAnswerRenderer: IRenderer { public string RenderHtml(Question q) { // render checkboxes } } 

在这种情况下,您将根据问题类型在构造函数中实例化渲染器。

如果问题类型在多种方式上与呈现不同,则选项1可能更可取。 如果渲染是唯一的区别,请考虑选项2。

将渲染逻辑分离到自己的类中是个好主意。 您不希望将渲染逻辑嵌入到应用程序的业务逻辑中。

我将创建一个名为QuestionRenderer的类,它接收一个Question,读取它的类型,并相应地输出渲染。 如果你使用的是ASP.NET,它可以输出webcontrols,或者你可以做一个输出HTML的服务器控件。

为什么没有QuestionRenderer类(实际上,它将是一个控件),它将一个Question公开为一个你可以设置的属性。

在render方法中,您可以根据问题类型决定要渲染的内容。

我不喜欢渲染细节与数据在同一个类中的想法。

因此,一种选择是让渲染方法只生成处理实际HTML渲染的一组用户控件之一。

另一种方法是拥有一个单独的类QuestionRenderer,它具有问题类型的各种子类(每个子类都会呈现正确的HTML)。

我想你想要的是一个IUserType,通过一些问题工厂将属性从hibernate映射转换为正确的控件类型。

可以在此处找到使用IuserType的示例: NHibernate IUserType

在示例中,它将blob转换为图像以供客户端使用,但具有相同的想法,您可以使用QuestionType创建页面。

您可以使用策略模式( Wikipedia )和工厂组合。

 public class Question { public Guid ID { get; set; } public int Number { get; set; } public QuestionType Type { get; set; } public string Content { get; set; } public Section Section { get; set; } public IList Answers { get; set; } private IQuestionRenderer renderer; public RenderHtml() { if (renderer == null) { QuestionRendererFactory.GetRenderer(Type); } renderer.Render(this); } } interface IQuestionRenderer { public Render(Question question); } public QuestionRendererA : IQuestionRenderer { public Render(Question question) { // Render code for question type A } } public QuestionRendererB : IQuestionRenderer { public Render(Question question) { // Render code for question type B } } public QuestionRendererFactory { public static IQuestionRenderer GetRenderer(QuestionType type) { // Create right renderer for question type } } 

只有公共属性需要包含在NHibernate中。

渲染肯定是一个UI问题,所以我将它与Question类分开,并添加一个工厂来隔离切换逻辑( QuestionControl基类inheritance自WebControl ,并将包含大部分渲染逻辑):

 RadioButtonQuestionControl: QuestionControl { // Contains radio-button rendering logic } CheckboxListQuestionControl: QuestionControl { // Contains checkbox list rendering logic } QuestionControlFactory { public QuestionControl CreateQuestionControl(Question question) { // Switches on Question.Type to produce the correct control } } 

用法:

 public void Page_Load(object sender, EventArgs args) { List questions = this.repository.GetQuestions(); foreach(Question question in Questions) { this.Controls.Add(QuestionControlFactory.CreateQuestionControl(question)); // ... Additional wiring etc. } } 

陈述显而易见:您可以使用工厂方法来获取所需渲染类的实例,并在其上调用渲染以获得所需的输出。

我将采用的方法是为您希望使用的问题创建一个单独的Control(如果您在MVC中,则为HtmlHelper方法)。 这将将问题表示为对象并在视觉上整洁地表示的问题分开。

然后,您可以使用主控件(或方法)根据呈现给它的Question实例的类型选择正确的呈现方法。