views:

198

answers:

6

I have a s/w design question. Say I have a windows form with some elements and I have a customer object. A customer can either be business, private or corporate for example.

Now, all the decisions of what is going to happen in the form will depend on the customer type. For instance, certain elements will be hidden, certain label text will be different, etc...events will respond differently.

Obviously, one way to code this will be to use CASE statement each time the decision needs to be made. Another way would be to have a Customer class and 3 other classed such as BusinessCustomer, PrivateCustomer and CorporateCustomer inherit from the base class. In the latter, a question then arises: how are you going to incorporate a windows from into it....

Please share your elegance, much thanks in advance.

EDITED: I just has an idea: can i embed forms within forms? My reqs don't dictate two windows being shown at once, so I dont have to use MDI. But to simplify my design based on some ppl's comments here, i would like to maintain 3 different customer forms, and embed inside the main form on the fly. That way three GUIs are separated and I won't have to deal with every control's visibility.

Now, anyone here had any experience with this? I am assuming I can just add a form to another form, such as this:

Form child_form = new Form();
parent_form.Controls.Add(child_form);
+1  A: 

If you do have 3 different windows, each handling a specific type of customer than there won't be much point in working with the base class or a contract. You could be smart though with a factory class that takes a customer class and determines the correct screen to use.

I've run into this quite a bit. I end up with a base window that handles the generic stuff and then extend it for each concrete type.

Joshua Belden
Sorry, I don't think this really answered your question but maybe will inspire someone to answer better.
Joshua Belden
That makes sense, except that in the flow of my application, when the form is created i don't know the Customer type yet. I don't want to deal with multiple form interface (the users wont like it for one thing). So, I guess my question is then: how do I dynamically load controls depending on the Customer type? Do I pass it on to another class? I am a bit lost here as I feel that Windows forms are not very MVC compliant. What happens to events then? etc etc. i would appreciate a simple example, but I can't find any online that actually work and run.
gnomixa
A: 

Just go for the three classes implementing some abstract Customer interface. In your app you would have a variable customer of type Customer and an object of a particular Customer type would be stored in there. Your GUI could then just rely on the interface and invoke methods on the customer variable regardless of what customer would be actually interacting with the GUI.

Have a look at this article.

Peter Perháč
I think this is the right approach, since it decouples the logic layer from the presentation layer (GUI)...
federubin
where would I load different elements then? Say I have 2 textboxes, 2 labels, 2 buttons, 1 datagrid. Where would the loading of these happen? From what i understand, this will be done in GUI, and each control's visibility would depend on the Customer type, such as btn.Visible = (Customer.type == CustomerType.Business);
gnomixa
There are various more/less pretty ways of achieving this, and as I have no insight into your system my advice will probably be rather vague. Say your btn should only be visible to business type customers because it does something `silly`. You could define boolean canDoSillyThings() method on Customer interface and then do: btn.Visible=customer.canDoSillyThings(); This is however not a great idea, as you would add responsibilities to your Customer interface that are not really Customers' concerns. But maybe in your particular case it would make sense to define this method for every customer...
Peter Perháč
+2  A: 

Those decisions really shouldn't be made in the GUI. You should have a ViewModel behind your GUI that makes those decisions, and that you write unit tests for. (Or a Presenter, or a Controller -- different names that all mean roughly the same thing: get the decisions out of the GUI class and into something you can unit test.)

Then your ViewModel would have e.g. a Boolean property for each element that the GUI would disable, and a method for each action you could take (CloseCustomerAccount() or whatever).

As long as the Form is created for a particular type of customer, and the customer won't change to a different type of customer during the lifetime of the form, you could just pass your Customer object (that stores all of the actual customer data) to the ViewModel's constructor, and then pass your ViewModel to your Form's constructor. The form could set all its Enabled properties right after it calls InitializeComponent(). On the other hand, if the customer type could change, then your ViewModel needs to expose some events for the Form to hook, so the form knows when to re-run its Enabling logic.

Your question then moves out of the Form and into the ViewModel. Do you have one ViewModel class with a bunch of case statements, or three ViewModel classes (maybe with a fourth that's a base class) that use polymorphism, and a factory method somewhere that decides, based on the particular customer, which ViewModel class to instantiate?

I'd let your code be the guide there. Start with the simplest approach, which is probably case statements. Write a unit test for every behavior you care about. If the case statements start to get too awkward, then add a ViewModel descendant for each customer type, and start extracting the case statements into virtual methods. Your tests will catch you if you make a mistake during the refactor.

Joe White
A: 

I would go with an MVP type approach and define CustomerPresenter class that exposes a boolean property that will derive your UI controls' enable/disable state via binding.

public class CustomerPresenter
{
  private Customer _customer;

  public CustomerPresenter(Customer customer)
  {
    _customer = customer;        
  }

  public bool EnableUI
  {
     get
     {
       //TODO: put your customer type basic logic here (switch statement or if/else)
       return _customer.Type == CustomerType.Business;
     }
  } 
}

public CustomerForm: WinForm
{
   private CustomerPresenter _customerPresenter;

   public CustomerForm(){};

   public CustomerForm(Customer customer)
   {
      _customerPresenter = new CustomerPresenter(customer);
   }      

   private void CustomerForm_Load(object sender, EventArgs e)
   {
      _someButton.DataBindings.Add("Enabled",_customerPresenter,"EnableUI");
   }

}
Mehmet Aras
+1  A: 

I agree with Joshua Belden's answer. Three separate forms for different kinds of customers would likely be the easiest to maintain.

Also, in case you didn't know already, you can derive from a Form class and tweak it in a derived Form class. This is even supported by the designer.

However, I'd like to offer an alternative:

The Bridge Pattern: separate an abstraction from its implementation so the two can vary independently.

What you could do is this:

Create three separate UIImplementation classes. These classes could tweak the UI, and the events for the Customer form. In order to gain access to the private members of the form, you would need to declare the UIImplementation classes as nested within the CustomerForm class. (Use partial classes to separate these into different files). If the form itself is significant, and the tweaks are insignificant, this may be good option. It's difficult to say.

Matt Brunell
+1 nice one for the maintainability concern. Well explained.
Peter Perháč
A: 

The solution should be driven by data, not your code.

So if you tag various controls as being visible in state "A", "B", or "C", then a single method could look at its state and know which controls to make visible or not.

You know you did it right when adding new controls for state "D" takes no code changes aside from those required to add the controls themselves.

You might also look at breaking the form down into 4 sub-forms, shared, sub-form a, sub-b, then displaying both "Shared" and "Sub-a" together on a parent form ...

Bill K