.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); } }
看起来很干净。 🙂
例如,您可以使用策略模式 :
-
让所有HTML渲染器都实现一个公共接口,例如
IQuestionRenderer
,方法名称为Render(Question)
。 -
在您的应用程序中有一个
Dictionary
的实例。 在初始化时填充它,可能基于配置文件。 -
对于问题的给定实例,请执行:
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
实例的类型选择正确的呈现方法。