代码之家  ›  专栏  ›  技术社区  ›  Chris

.NET类设计问题

  •  12
  • Chris  · 技术社区  · 15 年前

    我有一个名为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; }
    }
    

    基于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>
    

    目前,我在一个名为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 + "<br/>";
                answerLabel.Controls.Add(answerValue);
            }
    
            var stringWriter = new StringWriter();
            var htmlWriter = new HtmlTextWriter(stringWriter);
            wrapper.RenderControl(htmlWriter);
            return stringWriter.ToString();
        }
    
        #endregion
    }
    

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

    我觉得很干净。:)

    11 回复  |  直到 15 年前
        1
  •  11
  •   Konamiman    15 年前

    例如,您可以使用 the strategy pattern 以下内容:

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

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

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

    或者,您可以将方法命名为 RenderXXX 其中,xxx是问题类型,并使用反射调用它们。

        2
  •  12
  •   Gavin Miller    15 年前

    一般来说,每当您看到类型或枚举上的开关时,它意味着您可以将对象中的“类型”替换为“类型”—换言之,这是 polymorphism .

    实际上,这意味着您将为每个问题类型创建一个不同的类,并重写 RenderHTML() 功能。每个问题对象将负责知道它应该输出什么输入类型。

    好处是您可以删除switch语句并生成良好的基于OO的代码。收回是为每种问题类型添加一个类(在本例中,影响最小)。

        3
  •  5
  •   Ryan Brunner    15 年前

    这是使用对象继承来实现所需功能的经典案例。每当您看到一个大型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
        }
    }
    

    选项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
        }
    }
    

    在本例中,您将根据问题类型在构造函数中实例化呈现器。

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

        4
  •  3
  •   Dave Swersky    15 年前

    最好将呈现逻辑分离到自己的类中。您不希望将呈现逻辑嵌入到应用程序的业务逻辑中。

    我将创建一个名为questionrenderer的类,它接受一个问题,读取其类型,并相应地输出呈现。如果您使用的是ASP.NET,它可以输出WebControls,也可以执行输出HTML的服务器控件。

        5
  •  1
  •   Winston Smith    15 年前

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

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

        6
  •  1
  •   Jacob Mattison    15 年前

    我不喜欢将细节呈现在与数据相同的类中的想法。

    因此,一个选项是让您的呈现方法简单地生成一组处理实际HTML呈现的用户控件中的一个。

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

        7
  •  1
  •   AndrewB    15 年前

    我认为您需要的是一个IUserType,它通过某个问题工厂将属性从Hibernate映射转换为正确的控件类型。

    以下是使用iusertype的示例: NHibernate IUserType

    在这个示例中,它将一个blob转换为一个图像,以便在客户端使用,但是使用相同的思想,您可以使用questiontype创建页面。

        8
  •  1
  •   spa    15 年前

    你可以使用策略模式( 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<Answer> 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中。

        9
  •  1
  •   Jeff Sternal    15 年前

    渲染绝对是一个用户界面问题,所以我将它与 Question 类并添加一个工厂以隔离开关逻辑 QuestionControl 基类继承自 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<Question> questions = this.repository.GetQuestions();
        foreach(Question question in Questions) {
            this.Controls.Add(QuestionControlFactory.CreateQuestionControl(question));
            // ... Additional wiring etc.
        }
    }
    
        10
  •  0
  •   Krishna Kumar    15 年前

    说明了明显的问题:您可能可以使用工厂方法来获取所需呈现类的实例,并调用该实例的render来获取所需的输出。

        11
  •  0
  •   Paul Turner    15 年前

    我将采用的方法是为您希望使用的每个视觉样式创建一个单独的控件(如果您在MVC中,则创建一个HTMLHelper方法)。这分离了将问题表示为一个对象的关注点,并在视觉上整洁地表示它。

    然后,可以使用主控件(或方法)根据 Question 提供给它的实例。