Presentation Chooser

Select a screen appropriate for a particular domain object

31 August 2004

This is part of the Further Enterprise Application Architecture development writing that I was doing in the mid 2000’s. Sadly too many other things have claimed my attention since, so I haven’t had time to work on them further, nor do I see much time in the foreseeable future. As such this material is very much in draft form and I won’t be doing any corrections or updates until I’m able to find time to work on it again.

When the navigation between screens is directed by the presentation, the presentation can encode the sequence of screens to display directly. This way the presentation always knows which screen needs to be opened whenever a suitable event occurs. Sometimes, however, the screen that should be displayed depends upon information in the domain. The presentation does not know which screen to show, instead it knows that it should display a domain object in some window. The domain object cannot choose the screen, because that would violate Separated Presentation

A Presentation Chooser resovles what screen should be used for a particular domain object. The client knows it should open a new screen to display a particular domain object. The client asks the Presentation Chooser what screen should be used for this domain object and opens the returned window.

How it Works

In its simplest form, you can think of a Presentation Chooser as a dictionary lookup which indexes types of domain objects with types of screens.

Order => OrderWindow
Customer => CustomerWindow
PriorityCustomer => PriorityCustomerWindow
    

With this simple mode, looking up the appropriate screen is something like aPresentationChooser[domainObject.type].

Although this simple idea is the essence of Presentation Chooser, there are some common areas where things can get a bit more involved. Most of these stem from the fact that although most lookups are based entirely on the type of the domain object, not all are. As a result it's usually not worth exposing the type lookup nature of the Presentation Chooser to its clients, so rather than asking the client to pass in the type of domain object, instead pass in the object itself. This follows a general principle that an object should never ask a client to do what it can reasonably do itself. It also allows the Presentation Chooser to provide more sophisticated lookup rules should it need to.

The Presentation Chooser is usually a Service. During initialization some configuration module will initialize the Presentation Chooser with details about how screens and domain objects map together. Then during normal execution the screen uses the Presentation Chooser to lookup screens.

When to Use It

You need Presentation Chooser when you have to use a different screen class (usually a window, but could be an embedded panel in a form) in response to the same navigational flow. A simple example, as I use below, is where you have different types of objects in a single list and you need a different screen for each type.

Often the best way to handle this kind of situation is to use the same type of screen, but use hidden and disabled controls on that screen to block access to innapropriate data. If the variations between the types are small and you can use a similar screen without a jarring interface, then this can work well. When screen loads you interrogate the domain object to decide which controls to enable and display.

However using the same variable screen gives you less options for custom display of the underlying domain object. Sometimes the similarities will end up confusing the user more than helping them. In these cases it's better to use a different screen entirely, and the Presentation Chooser is then useful to determine which one.

Although I concentrate here on varying the screen depending on the domain data, the variation can also include overall customization factors, such as the user, the user's affiliation, multiple providers of the same application, or different aspects of the state of the interaction. All of these customizations may lead to using a different screen classes dynamically and thus imply an indirection in choosing the actual screen class to use. In these cases a Presentation Chooser is a good way of providing that indirection.

Both Presentation Chooser and Application Controller decouple the choice of screen class the triggerring navigation. Presentation Chooser is best when the domain object to be displayed is the primary variation, Application Controller is better when the state of the application is the primary variation. When both vary it makes sense to combine the patterns.

Example: Simple Lookup (C#)

The simplest example of a Presentation Chooser is one that has a simple lookup capability based solely on the type of domain object. Consider a application that displays information about musical recordings. The recordings are shown in a list, any one can be edited. However the underlying recordings need to edited differently depending on whether they are classical or popular recordings.

Figure 1: A window for picking recordings.

To react to this I have an event handler for clicking the edit button

private void btnEdit_Click(object sender, EventArgs e) {
  edit(Recordings[lstTitles.SelectedIndex]);
}
private void edit(IRecording recording) {
  Chooser.ShowDialog(recording);
}

The crux of this pattern is the edit method which asks a Presentation Chooser to display the dialog for editing the selected recording depending on whether it is a classical or popular recording.

In this case the Presentation Chooser is really just a dictionary lookup. During initialization we load the Presentation Chooser with details of the domain type and corresponding window to use.

class PresentationChooser...

  protected IDictionary presenters = new Hashtable();
  public virtual void RegisterPresenter(Type domainType, Type presentation) {
    presenters[domainType] = presentation;
  }

This initialization just simply stores the domain and presentation types in a dictionary. The lookup process is a little more complex. If I add a subtype of classical recording later on, I want it to be edited by a classical recording unless I've registered a screen for that subtype.

class PresentationChooser...

  public RecordingForm ShowDialog (Object model) {
    Object[] args = {model};
    RecordingForm dialog = (RecordingForm) Activator.CreateInstance(this[model], args);
    dialog.ShowDialog();
    return dialog;
  }
  public virtual Type this [Object obj] {
    get {
      Type result = lookupPresenter(obj.GetType());
      if (result == null) 
        MessageBox.Show("Unable to show form", "Error", MessageBoxButtons.OK,  MessageBoxIcon.Error);
      return result;
    }
  }
  private Type lookupPresenter(Type arg) 
  {
    Type result = (Type)presenters[arg];
    return (null == result) ? lookupPresenter(arg.BaseType) : result;
  }

I've written this to show an error message dialog if it can't find a registered screen. I can avoid needing to show this by providing a screen at the top of the hierarchy when I register the screens.

presentationChooser.RegisterPresenter(typeof(ClassicalRecording), typeof(FrmClassicalRecording));
presentationChooser.RegisterPresenter(typeof(PopularRecording), typeof (FrmPopularRecording));
presentationChooser.RegisterPresenter(typeof(Object), typeof (FrmNullPresentation));

Example: Conditional (C#)

Here I take the simple example above and give it a slightly more sophisticated spin by allowing some more dynamic chooser behavior. In this scenario I want most classical recording to be displayed by the regular classical display form, but to use a special display form for anything composed by Mozart. (I'm not sure why I'd want to do that, but I'm not so good at coming up with convincing examples on a Tuesday.)

Much of the interface to the Presentation Chooser is the same as the simpler version. The code to display a dialog for a recording is still just Chooser.showDialog(aRecording). However the implementation of the Presentation Chooser is a bit more involved.

I'm still using a dictionary indexed by type to store the information. However this time I want to have multiple screens for each domain object. I can capture each choice with a class.

class DynamicPresentationChooser…

  Type _registeredType;
  Type _presentation;
  ConditionDelegate _condition;
  public delegate bool ConditionDelegate(Object recording); 
  public PresentationChoice(Type registeredType, Type presentation, ConditionDelegate condition)  {
    this._registeredType = registeredType;
    this._presentation = presentation;
    this._condition = condition;
  }

Here I'm using the C# delegate to allow the client to pass in a boolean function to evaluate a condition against a domain object. The new lookup capability checks all the choices in the list in sequence returning the first one whose condition delegate evaluates to true with the given domain object.

class PresentationChoice...

  public override Type this[Object obj] {
    get {
      IList list= presenterList(obj.GetType());
      return (null == list) ? typeof(FrmNullPresentation): chooseFromList(list, obj);
    }
  }
  private IList presenterList(Type type) {
    IList result = (IList) presenters[type];
    if (null != result)
      return result;
    else if (typeof (object) != type)
      return presenterList(type.BaseType);
    else
      return new ArrayList();
  }
  private static Type chooseFromList(IList list, object domainObject) {
    foreach (PresentationChoice choice in list) 
      if (choice.Matches(domainObject)) return choice.Presentation; 
    return typeof(FrmNullPresentation);
  }

If nothing matches I return a Null Object.

To register the screens, I decided I wanted to maintain a compatable interface to the simple case above. To do that I allow registration with just the domain and screen types to work just like before with the help of a couple of convenience methods.

class DynamicPresentationChooser...

  public override void RegisterPresenter(Type domainType, Type presentation) {
    presenters[domainType] = new ArrayList();
    presenterList(domainType).Add (new PresentationChoice (domainType, presentation));
  }

class PresentationChoice...

  public PresentationChoice(Type domainType, Type presentation) : 
    this (domainType, presentation, null){
    _condition = new ConditionDelegate(TrueConditionDelegate);
  }
  public static bool TrueConditionDelegate(Object ignored) {return true;}

This way I can use the dynamic chooser in any sitatuion where I'd otherwise use the simple chooser.

I then use an additional registration method to put in dynamic options. I've set this up so I can only do this when I already have a default choice present. This makes the configuration interface a little more awkward than I'd like, but this way any problems show up in configuration so the result fails fast.

class DynamicPresentationChooser...

  public void RegisterAdditionalPresenter(Type domainType, Type presentation, PresentationChoice.ConditionDelegate condition) {
    Debug.Assert(
      null != presenterList(domainType), 
      String.Format("Must register default choice for {0} first", domainType));
    presenterList(domainType).Insert(0, new PresentationChoice(domainType, presentation, condition));
  }

I can then register the classes like this.

presentationChooser.RegisterPresenter(typeof(ClassicalRecording), typeof(FrmClassicalRecording));
presentationChooser.RegisterPresenter(typeof(PopularRecording), typeof (FrmPopularRecording));
chooser.RegisterAdditionalPresenter(
  typeof(ClassicalRecording), 
  typeof(FrmMozartRecording), 
  new PresentationChoice.ConditionDelegate(MozartCondition));