次の方法で共有


UI Composition Patterns

Figuring out the right name for something is one of the hardest things in software development. There is a lot to a name – the name conveys the concept; get the wrong name and you convey the wrong concept, leading to much confusion, developer irritation and mental pain.

We’re currently working on a new capability in Prism 2.0 that we’ve been calling ‘top-down’ or ‘view-first’composition. We’re not really sure whether either of those specific names clearly and succinctly describes what this capability is all about, and so we’ve been having many discussions on the team about what it is exactly that top-down/view-first composition achieves and how it relates to other types of composition. If we can be really clear on those two things, then maybe we can figure out what the best name for it is. In this post I’ll describe what top-down/view-first composition is and how it relates to other types of composition. At the end I hope you’ll agree that ‘view-first’is a reasonably good name for it. But, if you can think of a more appropriate name we’d dearly love to hear it!

Let’s start by defining composition as simply taking a bunch of ‘things’ and combining them to build an application. Different composition patterns describe different ways of composing an application, either by using different ‘things’, or by using a different way of combining the ‘things’. As always in software development, there’s more than one way to skin a cat (apologies to any cats or cat lovers reading this blog) so it isn't surprising that there are different composition patterns, each with its own advantages and disadvantages. Perhaps the most commonly used composition pattern though is the traditional ‘forms and controls’ pattern. Let’s walk through this pattern first and then we can contrast and compare other composition patterns to it.

Forms And Controls

Ah, good old-fashioned forms and controls. Where would we be without forms and controls? They have been the staple diet of UI tier application developers forever (well maybe not forever, but an awfully long time).

Using this composition pattern the developer will build an application by creating a number of forms. On each form they’ll drop one or more visual controls. They’ll configure the controls by setting various properties and then add code to the form to ‘wire’ the child controls up and implement the form’s business logic. The developer will often build controls of their own, and these will often be fairly sophisticated app-specific composite controls that contain many child controls. In fact, since forms are really just specialized types of controls, we can just think of this whole approach as ‘Control-Based Composition’.

The app is essentially the hierarchical combination of all of the controls and the code that wires them together. In this composition pattern then, the ‘things’ are visual controls, and the application is composed by assembling visual controls into higher-level controls and linking them together with code. It’s easy to provide a visual tooling experience for constructing applications in this way, and it’s relatively easy to understand the internal structure of an application by following its visual structure.

Control-based composition is pretty much the default experience that you get in almost all current tools and visual development environments, including Visual Studio, Blend, etc. Developers have been generally happy with this approach over the years – it’s simple, visual and designer friendly – so what’s the problem? Well, there are two related drawbacks to this approach – what I’ll call ‘Tight Coupling’ and ‘Overlapping Concerns’. Let’s take a look at each of these:

  • Tight Coupling – Since the developer explicitly chooses the controls at design-time, they can very easily make their application tightly coupled and inflexible. By choosing specific controls and writing code against that specific set of controls, it’s very easy to introduce strong dependencies that are dispersed throughout the entire codebase. These dependencies can be very hard to manage and can limit re-use opportunities and lead to static, brittle, hard to maintain, almost impossible to extend applications. They can make it very difficult to extend or modify the application without requiring (sometimes substantial) code changes, recompilation, and re-deployment of the application.
  • Overlapping Concerns – Most control models (all of the ones that I know of anyway) tend to define controls as single classes, and the control encapsulates both its UI and its business or presentation logic within this one class. From the consumer’s point of view, the class has an API and they code against that in the form or containing control. WPF and Silverlight have added the (absolutely fantastic) ability to re-style or re-template controls, which can help separate the controls UI from its behavior, but the control is still one class. For generic low-level controls (like buttons, text boxes, etc) this works great since they contain no business or presentation logic (just control logic). But for app-specific controls or forms (like, say, a CustomerList or CustomerContacts control) the control will typically mix visual aspects (e.g. BackgroundColor) with business or presentation logic aspects (e.g. AddCustomer, AddOrder). This can make it very difficult to test any business or presentation logic within the control without having to use some kind of UI automation or manual acceptance testing.

Neither of these two issues are intractable. The tight coupling problem can be addressed by adopting patterns that introduce loose coupling and that help manage dependencies – like modularity and dependency injection, etc. Using these patterns tends to alter the way in which the ‘things’ get composed into an application, but they tend not to affect what those things are. In this post, we’re going to focus mainly on the problem of overlapping concerns. We’ll see that the patterns that are used to address this issue generally lead to a change in the ‘things’ from which the application is composed.

Separated Presentation Patterns

One of the most popular remedies to the overlapping concerns issue is the introduction of a Separated Presentation Pattern (such as Model-View-Presenter, Model-View-ViewModel, Supervising Controller, etc, etc). I’m not going to detail the many different separated presentation patterns you could use here – it’s a big and somewhat nuanced subject – but suffice it to say that regardless of the actual pattern you choose, they all are concerned with separating out the purely visual aspects of a control from the business or presentation logic by encapsulating each into separate classes. For simplicity, I’ll call the class that encapsulates the visual aspects the ‘View’ class, and the class that encapsulates the presentation logic the ‘Presenter’ class (yes, different patterns use different names for these two classes, but the net result is the same so bear with me).

The two key benefits of adopting a separated presentation pattern are that you can unit test the behavioral/logic aspects of the control without having to automate it through the UI or do manual acceptance testing, and that you can more easily change the UI of the application while keeping the business or presentation logic completely untouched. There are other distinct benefits too, like being able to have your developers (who are typically bad at UI design) focus on app logic, while your UI designers (who are typically bad at writing app-logic) focus on the look and feel of the application. Another benefit is that since the View and the underlying Presenters are loosely coupled, the app can be more flexible and dynamic and choose (based on, say, the user’s role, current task or preferences) how best to render the application’s UI while re-using the exact same underlying Presenter.

With View and Presenter classes in the mix, what happens to the composition pattern? This is where things get interesting since we now have more ‘things’ with which we can compose. Essentially, the developer can choose to compose the application in one of two basic ways:

  • Compose the application from the Presenters, and then have the corresponding Views somehow created and wired up.
  • Compose the application from the Views, and then have the corresponding Presenters somehow created and wired up.

Not surprisingly, I’m going to call the first one ‘Presenter-First’ composition, and the second one (you guessed it!) ‘View-First’ composition. These two patterns represent two points on a spectrum and its possible to use both patterns in the same application, or to use a hybrid pattern that combines features from both, depending on your requirements or preference. In the next couple of sections, I’ll drill into each of these and describe their respective advantages and disadvantages.

Presenter-First Composition

In the Presenter-First composition pattern, the ‘things’ are primarily Presenters, and conceptually the application is composed by assembling/wiring Presenters together. The Presenters then pull in the Views that they depend on which then get inserted into the app’s UI.

With Presenter-First composition, the process goes something like this:

  1. The Presenter is created first.
  2. The Presenter (or some other entity) then creates a View and connects it to the Presenter.
  3. The View gets shown in the correct position in the UI.

There are many different ways of achieving each of these steps. Because of the variations that are possible at each of these steps, Presenter-First is really a general approach rather than just a single specific approach. Some variations to consider:

  • When a Presenter is created, its typically wired up to communicate with other Presenters (either in a peer-to-peer arrangement or in a parent-child arrangement) and with other Models and Services. Often, Dependency Injection is used to help create and wire up Presenters to other objects.
  • The Presenter itself can be responsible for explicitly creating a View for itself and then expose it through a property (e.g. MyPresenter.View). It’s not likely that the Presenter will create a specific View class – this would reduce flexibility and introduce tight coupling – it’s more likely that the Presenter will use a DI container or some form of configuration driven mechanism to create a suitable View. More often, the View creation mechanism is external to the Presenter and Views can be created implicitly (as is the case when using implicit DataTemplates in WPF) or explicitly by using a View factory of some sort that creates an instance of a specific View class according to some external configuration.
  • Once the View is created, it is ‘connected’ to the Presenter. I personally dislike any model where the Presenter has explicit knowledge of a View (say through an IView interface reference) regardless of how the View was created. I much prefer the model where the View is an observer of the Presenter and the Presenter goes about its business altering and managing its state, according to the presentation logic it encapsulates, and the View updates itself accordingly through bindings and behaviors. I’ll be posting much more on this in a later post…

To some developers, presenter-first composition feels more natural and ‘pure’ since the View creation can be abstracted away allowing them to focus on the logical non-UI structure (Presenters and other non-UI Models and Services) of the application. For any one particular use case or screen in the application, one or more presenters will be in play and the developer can write unit tests for this combination of presenters without having to test by automating through the UI. In extreme cases, the application may even be driven completely ‘headless’ and not require a UI at all, and be able to be driven through a command line interface (nice for the purists but not generally useful IMHO).

The downside with this approach is that it tends to be complex, making it very difficult to understand how the various pieces of the application get created and connected together. Since the UI is constructed at run-time as a result of the interaction of many components, to understand the structure of an application, you often have to spend many happy hours in the debugger tracing through everything and seeing what gets created, when and by whom. It also is very difficult to provide any kind of visual tooling experience for applications constructed in this way.

You can see this composition pattern in action in Prism 1.0 in the UIComposition quick-start. This example implements a simple project management application made up of 2 separate modules – an Employee module that provides general information on a list of employees, and a Projects module that provides details on their current projects. The Projects module has a ProjectsListPresenter, ProjectsListView and a ProjectService. A controller class, the EmployeesController class, is used to coordinate and stitch everything together.

clip_image001

When the user selects a particular employee in the UI, the EmployeesController constructs a Presenter using the container:

 IProjectsListPresenter projectsListPresenter = this.container.Resolve<IProjectsListPresenter>();

The DI container constructs the Presenter class and injects any dependencies that it may have. In this case, the Presenter has dependencies on a ProjectsListView and a ProjectService. The container dutifully creates those objects and passes references to a them into the Presenter’s constructor.

 public class ProjectsListPresenter : IProjectsListPresenter
 {
     private readonly IProjectService projectService;
     public IProjectsListView View { get; set; }
  
     public ProjectsListPresenter( IProjectsListView view, IProjectService projectService )
     {
         this.View = view;
         this.projectService = projectService;
     }
 }

Note that all of these types are registered with the container in the Projects module’s Initialize method so the container knows how to resolve everything:

 this.container.RegisterType<IProjectService, ProjectService>();
 this.container.RegisterType<IProjectsListView, ProjectsListView>();
 this.container.RegisterType<IProjectsListPresenter, ProjectsListPresenter>();

Now, the astute reader might have spotted that in the code above, the View is technically constructed before the Presenter. That’s just an artifact of the fact that we’re using constructor injection in this example. The key thing is that the Presenter is logically created first, and that the Presenter has a dependency on the View (rather than the other way round).

With the Presenter and its dependent View and Service constructed and wired up, the controller needs to show the Presenter’s View in the UI somehow. To do this, we use Regions. Regions are essentially named placeholders in the UI into which arbitrary Views can be inserted. In the EmployeeDetailsView we defined a region called ‘DetailViewsRegion’:

 <TabControl cal:RegionManager.RegionName="DetailViewsRegion" … >

This view is in the Employees module and doesn’t know anything about the specific ProjectListView that we want to show here. The controller is responsible for making this association. To do that it goes and looks for this region, using the region manager, and then inserts the view into it:

 IRegion region = detailsRegionManager.Regions["DetailViewsRegion"];
 region.Add( projectsListPresenter.View, "CurrentProjectsView" );
 detailsRegion.Activate( detailsPresenter.View );

The Region is passive in the sense that it has no knowledge that any specific view (or any view at all) will be shown – it’s left to the presenter or controller to determine which, if any, view is shown and to inject that view into the region. This makes it somewhat difficult to understand the structure of the application just by looking at the UI because the placeholders are just passive named regions – in other words, the logical structure of the application can’t be easily determined from the visual structure. In addition, if a region required by module is not defined in the hosting application, the module may fail unless the developer checks for the existence of the region before trying to inject the View.

The following diagram shows the sequence of events for Presenter-First composition.

Presenter-First Composition 

Because this approach focuses on creating and connecting the Presenters, Models, Services and other non-UI components first, and then creating Views and pushing them up into the UI second, we’ve also been calling this approach Bottom-Up Composition. I think Presenter-First is a more accurate name though…

View-First Composition

As its name implies, View-First Composition focuses on composing the application first and foremost from Views rather than Presenters – the ‘things’ are primarily Views – and conceptually the application is composed from Views. The Views then pull in the Presenters that they depend on.

With View-First composition, the process goes something like this:

  1. The View is created first.
  2. The View (or some other entity) then creates a Presenter and connects it to the View.
  3. The View gets shown in the correct position in the UI.

Again, there are many different ways of achieving each of these steps and View-First is really a general approach to composition.

The principal difference between View-First and Presenter-First is that the View has a dependency on the Presenter, rather than the other way round. View-First is a composition pattern that lies somewhere between the traditional Control-Based composition and Presenter-First composition. One of its primary benefits is that it makes it easier to construct applications than with the Presenter-First approach, while retaining the loosely coupled, pluggable, unit-testable benefits of using a separated presentation pattern. This pattern also makes it much easier to understand the structure of a given application by allowing you to follow the visual structure, reducing the need to track code execution in order to understand how everything is created and connected together.

Let’s see View-First composition in action. We’ll use the same example as above and show how the same application would be composed using this approach.

Disclaimer! We’re still working on this so some of the code below is just an illustration of how this capability could work, and we may or may not implement it as shown here – your feedback would be great to have on this.

When the application runs, the EmployeeDetailsView is shown. As before, this view defines a region where the project list for the currently selected employee will be displayed.

 <TabControl cal:RegionManager.RegionName="DetailViewsRegion" … >

Whereas in Presenter-First composition the region is passive and waits for a view to be injected into it by a controller, in View-First composition the region will proactively go and create any views that are registered to be shown in this region. When the EmployeeDetailsView is shown, the region manager will go and see if any views have been registered for the DetailViewsRegion. Views are registered with the region manager in the module’s Initialize method:

 this.regionManager.RegisterViewForRegion( "DetailViewsRegion", typeof( ProjectsListView ) );

You can think of this as the equivalent of registering a type with a DI container so it will know how to instantiate dependencies, except here we’re registering a View type against a named region so the region manager will know how to instantiate the views required for that region. Note that the parent view and the child view are still loosely coupled and we can still plug in any implementation of a view that we choose.

The ProjectsListView is now instantiated by the RegionManager, but what about the Presenter? In View-First composition, the ProjectsListPresenter is considered a dependency of the view. This dependency is expressed in the ProjectsListView declaratively like this:

 <UserControl x:Class="UIComposition.Modules.Project.ProjectsListView"
     cal:Presenter.Type="{x:Type UIComposition.Modules.Project.IProjectsListPresenter}" … >

After the view is created, an instance of the required presenter is created using the DI container, which incidentally will create in turn any dependencies that the Presenter has (in this case the ProjectService). Once the Presenter is created it is connected to the view by setting it as the view’s data context (so that any binding or command expressions in the view will be directed to the presenter).

The following diagram shows the sequence of events for View-First composition.

View-First Composition

Since composition flows from the View down to the Presenters and then to the Models, Services and other non-UI components, we've been also been calling this 'Top-Down' composition. Again, I think View-First is probably a better name though.

And that’s pretty much it. With View-First composition, we have less code for the same end-result but the chief benefit is that it’s much easier to follow what happens and when. The key thing to remember is that the View is created first, then the Presenter (which is considered a dependency of the View) then any dependencies that the Presenter has. This pattern is very similar to the traditional and familiar control-based composition pattern, but with the added benefit that it’s loosely coupled and separates the UI from the business and presentation logic. It is also easy to see how this pattern can be supported by visual tools – imagine a placeholder control that you can drop onto your form and then configure to go and pull in whatever View is registered by the pluggable modules.

oO------Oo

Hopefully this all makes sense. Once you move away from control-based model where everything is in one class, to a separated presentation model which introduces additional classes, things can get complicated pretty quickly. These patterns can help make sense of this additional complexity and allow you to realize the benefits of loose coupling and a separation of concerns without imposing too much overhead...

Comments

  • Anonymous
    December 06, 2008
    PingBack from http://www.alvinashcraft.com/2008/12/06/dew-drop-weekend-edition-december-67-2008/

  • Anonymous
    December 09, 2008
    It sounds interresting and I'd like to test this. But, for a better feedback, you should release some code on the prism project on codeplex, shouldn't you?

  • Anonymous
    December 10, 2008
    The latest Prism drop on CodePlex includes support for Presenter-First and View-First composition. You can download the latest (drop 7) from here: http://www.codeplex.com/CompositeWPF/Release/ProjectReleases.aspx?ReleaseId=20206. The UIComposition quick-start is the best place to start. As I mentioned above, the code isn't quite the same as I outlined above - I'm hoping that we can close the gap in the next release - but we'd love to hear your feedback.

  • Anonymous
    December 10, 2008
    This was a great overview! Thanks for sharing.

  • Anonymous
    December 11, 2008
    My bad. I was looking at the "source code" tab on codeplex so thanks for the link, I'll definitely check this.

  • Anonymous
    December 15, 2008
    Thanks for explaining this complicated concepts in an easy and clear writing. Have two questions. When will the SL Prism framework be ready for production? And how is it going with the Helix framework - can't wait for the next "drop"

  • Anonymous
    December 15, 2008
    The comment has been removed

  • Anonymous
    February 11, 2009
    In this post, I'm going to describe an implementation of ICollectionView for Silverlight that allows