.NET类设计问题

Chr*_*ris 12 .net c#

我有一个名为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<Answer> Answers { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

基于QuestionType枚举属性,我想呈现以下内容(仅作为示例):

<div>[Content]</div>
<div>
   <input type="[Depends on QuestionType property]" /> [Answer Value]
   <input type="[Depends on QuestionType property]" /> [Answer Value]
   <input type="[Depends on QuestionType property]" /> [Answer Value]
   ...
</div>
Run Code Online (Sandbox Code Playgroud)

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

有什么想法吗?

编辑:感谢大家的答案!

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

public interface IQuestionRenderer
{
    string RenderHtml(Question question);
}
Run Code Online (Sandbox Code Playgroud)

以下实施:

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 + "<br/>";
            answerLabel.Controls.Add(answerValue);
        }

        var stringWriter = new StringWriter();
        var htmlWriter = new HtmlTextWriter(stringWriter);
        wrapper.RenderControl(htmlWriter);
        return stringWriter.ToString();
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

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

public class Question
{
    private Dictionary<QuestionType, IQuestionRenderer> _renderers = new Dictionary<QuestionType, IQuestionRenderer>
    {
        { 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<Answer> Answers { get; set; }

    public string RenderHtml()
    {
        var renderer = _renderers[Type];
        return renderer.RenderHtml(this);
    }
}
Run Code Online (Sandbox Code Playgroud)

看起来很干净我.:)

Gav*_*ler 12

一般来说,每当你看到Type或Enum上的开关时,就意味着你可以在对象中替换为"Type" - 换句话说,就是多态的情况.

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

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

  • +1表示多态性超过switch语句.同时促进单一责任. (2认同)

Kon*_*man 11

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

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

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

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

或者,您可以使用名为RenderXXXXXX的方法,其中XXX是问题类型,并使用反射调用它们.


Rya*_*ner 5

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

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

选项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<Answer> 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
    }
}
Run Code Online (Sandbox Code Playgroud)

选项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<Answer> 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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