Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>This looks like a prime example for polymorphism.</p> <p>Instead of trying to do a type switch on your <code>ICriteria</code> implementation, why don't you add a method to <code>ICriteria</code> (or possibly a virtual method to some common base class of all <code>ICriteria</code> implementations), and just call that?</p> <p>Obviously the implementation of this method would need access to objects that do not belong in your <code>ICriteria</code> instances, but that is a problem you can solve using other design patterns according to the specifics of your scenario.</p> <p><strong>Update:</strong></p> <p>Here's a complete solution, incorporating the code you posted:</p> <p>Create a new interface <code>ICriteriaView</code> which models the view (in your case a <code>Form</code>) where <code>ICriteria</code> are displayed. The form needs to do some processing depending on the exact interface that criteria implement, so add a method with one overload for each interface that exists in your code. <em>Do not add an overload for <code>ICriteria</code> itself.</em> <strong>[1]</strong></p> <pre><code>interface ICriteriaView { void ProcessCriteria(IChoices criteria); void ProcessCriteria(ITextCriteria criteria); } </code></pre> <p>Your form will implement this interface, providing methods where suitable processing for each subtype of <code>ICriteria</code> will occur:</p> <pre><code>class MyForm : ICriteriaView { public void ProcessCriteria(IChoices criteria) { this.SaveMultipleChoiceValues(criteria); } public void ProcessCriteria(ITextCriteria criteria) { // do nothing } private void SaveMultipleChoiceValues(IChoices criteria) { foreach (DataGridViewRow row in dgvCriteriaControls.Rows) { if (row == dgvCriteriaControls.Rows[dgvCriteriaControls.Rows.Count - 1]) continue; //multipleChoice.AddChoice(row.Cells["Name"].Value.ToString()); string choice = row.Cells["Name"].Value.ToString(); criteria.AddChoice(choice); } } } </code></pre> <p>Each implementation of <code>ICriteria</code> will need to implement a method which calls the appropriate <code>ICriteriaView</code> overload for its type. This is where the "redirection magic" happens: we will use polymorphism to get the compiler to "discover" the actual type of <code>ICriteria</code> our object is, and then use method overloading on <code>ICriteriaView.ProcessCriteria</code> to access the appropriate code.</p> <pre><code>interface ICriteria { void PerformProcessingOn(ICriteriaView view); } interface IChoices : ICriteria { } interface ITextCriteria : ICriteria { } </code></pre> <p>And this is where the dispatch to the appropriate overload happens:</p> <pre><code>class MultipleChoice : IChoices { public PerformProcessingOn(ICriteriaView view) { view.ProcessCriteria(this); } } class SimpleInput : ITextCriteria { public PerformProcessingOn(ICriteriaView view) { view.ProcessCriteria(this); } } </code></pre> <p>Then, your code would do:</p> <pre><code>// Get selected criteria var selectedCriteria = cmbType.SelectedItem as ICriteria; // Here's where polymorphism kicks in selectedCriteria.PerformProcessingOn(this); // Finally, code that runs the same for all objects _category.AddCriteria(selectedCriteria); selectedCriteria.LabelText = txtLabeltext.Text; this.Close(); </code></pre> <p><strong>Maintenance:</strong></p> <p>Whenever you add a new <code>ICriteria</code> sub-interface implementation, the definition of <code>ICriteria</code> will force you to implement the <code>PerformProcessingOn</code> method on it. Inside that method, all you can do really is call <code>view.ProcessCriteria(this)</code>. In turn, this will force you to implement an appropriate <code>ProcessCriteria</code> overload in <code>ICriteriaView</code> and <code>MyForm</code>.</p> <p>As a result, we have achieved two important objectives:</p> <ol> <li>The compiler will not allow you to add a new <code>ICriteria</code> implementation without specifying exactly how that implementation should interact with <code>ICriteriaView</code>.</li> <li>It is easy to discover from source code <em>exactly</em> what <code>MyView</code> does with e.g. <code>IChoices</code> when reading the code for <code>MultipleChoice</code>. The structure of the code leads you to <code>MyForm.SaveMultipleChoiceValues</code> "automatically".</li> </ol> <p><strong>Notes:</strong></p> <p><strong>[1]</strong> The choice of adding an overload for <code>ICriteria</code> itself or not is really a tradeoff:</p> <ul> <li><p>If you <em>do</em> add one, then code like this:</p> <pre><code>class MultipleChoice : IChoices { public PerformProcessingOn(ICriteriaView view) { view.ProcessCriteria(this); } } </code></pre> <p>will compile successfully always, because even if there is no <code>ICriteriaView.ProcessCriteria(IChoices)</code> overload there will still be the <code>ICriteriaView.ProcessCriteria(ICriteria)</code> overload that the compiler can use.</p> <p>This means that, when adding a new <code>ICriteria</code> sub-interface implementation, the compiler will no longer <em>force</em> you to go check if the implementation of <code>ICriteriaView.ProcessCriteria(ICriteria)</code> really does the right thing for your new implementation.</p></li> <li><p>If you <em>do not</em> add one, then the moment you write <code>view.ProcessCriteria(this);</code> the compiler will <em>force</em> you to go check (and update) <code>ICriteriaView</code> and <code>MyForm</code> accordingly.</p></li> </ul> <p>In this scenario, and with the information you have provided, I believe that the appropriate choice would be the last one.</p> <p><strong>[2]</strong> As you can see above, the implementation of <code>ICriteria.PerformProcessingOn</code> inside <code>MultipleChoice</code> and <code>SimpleInput</code> looks exactly the same. If these two classes have a common base (which is quite possible in practice), you might be tempted to move the "duplicated" code into that base. <strong>Do not do that; it will cause the solution to break.</strong></p> <p>The tricky part is that inside <code>MultipleChoice</code>, when you do <code>view.ProcessCriteria(this);</code> the compiler can infer that the static type of <code>this</code> is <code>IChoices</code> -- this is where the redirection happens! If you move the call to <code>ProcessCriteria</code> inside a hypothetical base class <code>CriteriaBase : ICriteria</code>, then the type of <code>this</code> will become <code>ICriteria</code> and the dispatch of the call to the appropriate <code>ICriteriaView.ProcessCriteria</code> overload will no longer work.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload