2009-10-30 11 views
12

私は、タイプと呼ばれるプロパティを持つ質問というクラスがあります。このタイプに基づいて、特定の方法(複数選択=ラジオボタン、複数回答=チェックボックスなど)でhtmlに質問をレンダリングします。質問型に応じてサブメソッドを呼び出すRenderHtmlメソッドを1つ用意して始めましたが、インターフェイスを実装する個々のクラスにレンダリングロジックを分離することを検討しています。しかし、このクラスはHibernateを使用してデータベースに永続化され、インターフェイスの実装はプロパティに依存しているため、クラスをレイアウトするにはどうすればよいか分かりません。問題の.NETクラスの設計に関する質問

クラス:QuestionType列挙型プロパティに基づいて

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

、私は次の(単なる例)をレンダリングしたいと思います:現在

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

修正質問クラスはそうのような内部の辞書を使用しています。

は、私は、次のインターフェイスを使用して戦略パターンと一緒に行くことになりました

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

かなりきれいに見えます。 :)

+1

スイッチステートメントが非常に汚いです。その95%がそれを行う良い方法があります。 –

+0

それが私がより良い方法を模索していた理由だろう。 :) – Chris

答えて

11

考える:

  1. をすべてのあなたのHTMLレンダラーは、メソッド名Render(Question)で、たとえばIQuestionRendererのために、共通のインタフェースを実装してもらいます。

  2. Dictionary<QuestionType, IQuestionRenderer>のインスタンスをアプリケーションに作成します。おそらく設定ファイルに基づいて、初期化時に設定します。質問の特定のインスタンスのために

  3. 、実行します。renderers[question.Type].Render(question)

それとも、あなたはXXXは、質問タイプですRenderXXXという名前のメソッドを持って、反射を利用して、それらを呼び出すことができます。

+0

#1は私が答えを待つときに始まった経路です。私が正しい軌道に乗っているように思えます... – Chris

+0

http://www.dofactory.com/Patterns/PatternStrategy.aspx参照用 – Pratik

1

あなたが設定できるプロパティとしてQuestionを公開するQuestionRendererクラス(実際には、コントロールになる)を持たないのはなぜですか。

レンダリングメソッドでは、質問タイプに基づいてレンダリングするものを決定できます。

+1

私は実際にインターフェイスと同様の何かを考えていましたが、インターフェイスの各実装はswitch文の多相性のために特定のQuestionType – Chris

1

レンダリングの詳細がデータと同じクラスにあるとは思いません。

したがって、実際のHTMLレンダリングを処理する一連のユーザーコントロールの1つをレンダリング方法で生成することもできます。

もう1つは、質問タイプのさまざまなサブクラス(それぞれが正しいHTMLを表示する)を持つ別のクラスのQuestionRendererを持つことです。

3

レンダリングロジックを独自のクラスに分けることをお勧めします。アプリケーションのビジネスロジックにレンダリングロジックを埋め込む必要はありません。

QuestionRendererという名前のクラスを作成し、Questionを取り込み、型を読み取り、それに応じてレンダリングを出力します。 ASP.NETを使用している場合は、Webコントロールを出力することも、HTMLを出力するサーバーコントロールを使用することもできます。

12

一般的に言えば、タイプまたは列挙型のスイッチが表示されているときは、オブジェクトを「タイプ」として置き換えることができます。つまり、polymorphismの場合です。

実際には、質問タイプごとに異なるクラスを作成し、RenderHTML()関数を上書きするということです。各Questionオブジェクトは、どの入力タイプを出力すべきかを知る責任があります。

switch文を削除して、良いOOベースのコードを生成できるという利点があります。ドローバックは、すべてのQuestionタイプ(この場合は最小限のインパクト)のクラスを追加することです。

+2

+1を処理します。また、単一責任を促進する。 –

+1

@Dave:あなたはどこかに切り替える必要があります...継承でインスタンス化するだけです。私は構図を好むでしょう(下のコナミマンのような戦略パターン) –

+0

コナミマンの答えは今のところです。それは正しいものです...クリスは言います。 –

0

明らかに:ファクトリメソッドを使用して、必要なレンダリングされたクラスのインスタンスを取得し、必要な出力を得る。

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

オプション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は、質問タイプがレンダリングよりも多くの点で異なる場合にはおそらく好ましいでしょう。レンダリングが唯一の違いである場合は、あなたが例えばthe strategy patternを使用することができます。オプション2

+0

質問自体をサブクラス化することを考えましたが、NHibernateがプロパティタイプに基づいて特定のサブタイプを返すようにする方法がわかりません。理想的には、私はデータベースからオブジェクトを取得した後にキャストの束をしたくありません。 – Chris

+0

この場合、戦略パターン(オプション2)が理想的です。質問タイプを社内または私的な財産(またはそれが必要な場合は一般の人)として保管し、その値に基づいてIRendererをインスタンス化することができます。 –

0

私が取るアプローチは、あなたの質問を使用してレンダリングしたいそれぞれのビジュアルスタイルについて、別々のControl(またはMVCの場合はHtmlHelperメソッド)を作成することです。これは、質問をオブジェクトとして表現し、それを視覚的にきれいに表現するという心配を分けます。

次に、マスターコントロール(またはメソッド)を使用して、提示されたQuestionインスタンスのタイプに基づいて正しいレンダリング方法を選択できます。

1

レンダリングは間違いなくUIの関心事であるので、私は(QuestionControl基底クラスがWebControlから継承し、レンダリングロジックの大部分を含んでいるでしょう)Questionクラスからということを分離し、スイッチングロジックを分離するために工場を追加したいです:

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. 
    } 
} 
+0

これをUserTypeに追加すると、コントロールが作成されます。 – AndrewB

+0

私はNHibernateをまだ十分に調べていませんが、UIコントロールへの直接マッピングは非常に興味深いものです。 –

1

私が何をしたいことは、いくつかの質問の工場を経由して、正しい制御型にHibernateマッピングからプロパティを変換IUserTypeだと思います。

IuserTypeの使用例は、ここで見つけることができます: NHibernate IUserType

の例では、クライアント側で使用するための画像にブロブを変換しますが、同じ考え方で、あなたのページが作成することができますQuestionTypeを使用します。

1

戦略パターン(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にパブリックプロパティのみを含める必要があります。

+0

くそ、私はあまりにも遅く書くようです:-) – spa

関連する問題