Подтвердить что ты не робот

Вопрос проектирования класса .NET

У меня есть класс под названием "Вопрос", который имеет свойство "Тип". Основываясь на этом типе, я хочу передать вопрос 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>

В настоящее время у меня есть один большой оператор switch в функции RenderHtml(), которая выполняет грязную работу, но я хотел бы переместить ее на что-то более чистое. Я просто не знаю, как это сделать.

Любые мысли?

EDIT: Спасибо всем за ответы!

В итоге я перешел к шаблону стратегии, используя следующий интерфейс:

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
}

Измененный класс 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);
    }
}

Выглядит довольно чисто для меня.:)

4b9b3361

Ответ 1

Вы можете, например, использовать шаблон стратегии:

  • Пусть все ваши средства визуализации HTML реализуют общий интерфейс, например IQuestionRenderer, с именем метода Render(Question).

  • У вас есть экземпляр Dictionary<QuestionType, IQuestionRenderer> в вашем приложении. Заполните его во время инициализации, возможно, на основе файла конфигурации.

  • Для данного экземпляра вопроса выполните: renderers[question.Type].Render(question)

Или вы могли бы иметь методы с именем RenderXXX, где XXX - тип вопроса, и вызывать их с помощью отражения.

Ответ 2

Вообще говоря, всякий раз, когда вы видите переключатели типа или Enum, это означает, что вы можете заменить объекты на "Тип" - иначе говоря, на случай polymorphism.

Это означает, что вы создадите для каждого типа вопросов другой класс и переопределите функцию RenderHTML(). Каждый объект Question будет отвечать за знание того, какой тип ввода он должен выводить.

Преимущества в том, что вы удаляете оператор switch, а также создаете хороший код на основе OO. Обратный отсчет состоит в том, что вы добавляете класс для каждого типа Вопроса (в этом случае минимальный эффект.)

Ответ 3

Это классический случай использования наследования объектов для достижения желаемого. Каждый раз, когда вы видите, что оператор большого оператора переключает тип объекта, вы должны рассмотреть некоторую форму подкласса.

Я вижу два подхода, в зависимости от того, насколько "распространены" эти типы вопросов, действительно ли рендеринг - единственное различие между ними:

Вариант 1 - Подкласс класса Question

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

Хорошо отделить логику рендеринга в своем классе. Вы не хотите, чтобы в бизнес-логику вашего приложения была встроена логика визуализации.

Я бы создал класс под названием QuestionRenderer, который принимает вопрос, читает его тип и выводит соответственно. Если вы используете ASP.NET, он может выводить webcontrols, или вы можете сделать серверный элемент управления, который выводит HTML.

Ответ 5

Почему бы не иметь класс QuestionRenderer (на самом деле это будет элемент управления), который предоставляет Question как свойство, которое вы можете установить.

В методе рендеринга вы можете решить, что делать на основе типа вопроса.

Ответ 6

Мне не нравится идея отображения деталей в том же классе, что и данные.

Итак, одним из вариантов было бы, чтобы ваш метод рендеринга просто сгенерировал один из наборов пользовательских элементов управления, которые обрабатывали фактическую визуализацию HTML.

Другим было бы иметь отдельный класс QuestionRenderer, который имел бы различные подклассы для типов вопросов (каждый из которых отображал бы правильный HTML).

Ответ 7

Отрисовка определенно относится к пользовательскому интерфейсу, поэтому я отделяю это от класса Question и добавляю factory, чтобы изолировать логику коммутации (базовый класс 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.
    }
}

Ответ 8

Я думаю, что вам нужен IUserType, который преобразует свойство из сопоставления спящего режима в правильный тип управления через некоторый вопрос factory.

Пример использования IuserType можно найти здесь: NHibernate IUserType

в примере он преобразует blob в образ для использования на стороне клиента, но с той же идеей вы можете создать свою страницу с помощью QuestionType.

Ответ 9

Вы можете использовать шаблон стратегии (Wikipedia) и factory в комбинации.

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 должны быть включены только общедоступные свойства.

Ответ 10

Указание очевидного. Вероятно, вы можете использовать метод factory, чтобы получить экземпляр требуемого класса rendered и вызвать рендеринг для этого, чтобы получить требуемый вывод.

Ответ 11

Подход, который я хотел бы сделать, - создать отдельный элемент управления (или метод HtmlHelper, если вы в MVC) для каждого визуального стиля, который вы хотите, чтобы ваши вопросы отображались с использованием. Это отделяет проблемы представления вопроса как объекта и представляет его визуально аккуратно.

Затем вы можете использовать главный элемент управления (или метод), чтобы выбрать правильный метод рендеринга, основанный на типе экземпляра Question, представленном ему.