Showing posts with label MVC. Show all posts
Showing posts with label MVC. Show all posts

Wednesday, May 07, 2008

Design Rules for Model-View-Presenter

In my current project the MVP pattern is used in the supervising controller mode. The MVP pattern is an adaption of the old MVC pattern that incorporates that the capabilities of WinForms views have become smart enough to lift some of the burdens previously implemented in the controller. This applies to e.g. handling click events and data-binding; a presenter only injects the model into the view which exploits data-binding, while a controller explicitly sets the values of each control in the view. In short, a presenter should handle the use-case logic only, not the view logic.

Each view must implement an interface that defines all interactions that the presenter has with the view. This ensures a clear separation between the presenter and the view, and has the advantage of making the presenter easy to unit-test using mocking. The view never calls other modules and services directly; it must always use an event to request that the presenter provides the needed data or service.

This project uses CAB/SCSF to generate the view modules. The generated code is not quite as I would like it, I prefer that the view has no knowledge of the presenter and no direct access to it either as this gives a cleaner separation between views and presenters. I prefer that the view interface defines a set of events that is used to communicate view events to the presenter. This makes it harder for unknowledgable programmers and code monkeys to misuse the presenter reference; making shortcuts and ignoring the reasons for using patterns is very common and leads to unmaintainable code. Read Jeremy Miller's View to Presenter Communication for further opinions on this topic.

The following MVP design rules have a CAB flair, but are generally applicable to any MVP implementation:

1. All views should have a XxxView suffix: TaskView/ITaskView
2. All presenters should have a XxxPresenter suffix: TaskViewPresenter
3. Let the presenter do all use-case processing, but keep GUI control details in the view
4. All presenter methods called by the view must start with OnXxx() as they are events by design (MVP)
5. Calls from the view to the presenter should be kept at an absolute minimum, and used for "event" type calls only: _presenter.OnViewReady();
6. It is FORBIDDEN to use the presenter reference to access the model or services directly, no return values from presenter methods
7. Calls from the presenter to the view MUST go via the interface only
8. No methods in the view shall be public unless they are defined in the interface
9. It is FORBIDDEN to access the view from anywhere but the presenter, except for loading and showing the view from the CAB ModuleController
10. The interface methods shall have long meaningful names (not "SetDataSource") based on the domain language of the use-case
11. The interface should contain methods only, no properties - afterall the presenter drives the use-case by calling methods, not by setting data
12. All data in the MVP component should be kept in the model, no data should exist only as properties of UI controls
13. The methods of the view interface should not contain UI control name references (e.g. AddExplorerBarGroup) as this makes the presenter know too much about the implementation technology of the view
14. Stick with domain naming in the view methods (e.g. AddTaskGroupHeader), this makes the code easier to understand and the tests self-describing

Rule 11 and 12 might surprise you if your perception of the model is that it contains business entities only - traditional database-driven design thinking. The model is rather business process documents that are involved in the use-case, i.e. domain entity and value objects. Even views used for searching have a model, it is a specification or query object. Jeremy has some guidelines on assigning responsibilities in MVP.

Rule 6 helps adhering to the "tell, don't ask" principle - properties make it far too easy to ask, rather use methods on the view to tell it what to do. I've seen design-time view data-binding directly against presenter properties, not exactly separation of concerns. Neither is it very testable, as there is no interaction from the presenter through the view's interface when the view asks the presenter directly. When mocking the view, there will be no recorded / testable interaction.

Using long meaningful names in the view interface has the nice side-effect of making the presenter unit-tests a kind of mini documentation.

I wish there was a FxCop Contrib project on CodePlex like for EntLib, CAB/SCSF and ASP.NET MVC. Then maybe I would write and publish some of these MVP design rules as custom FxCop rules.

Tuesday, October 17, 2006

Enter Supervising Presenter, exit MVP

This june I wrote about how we used model reference data and the Model-View-Presenter pattern in a WinForms container control with pluggable views. What I did not say was that we did not implement pure MVP, but rather a modified version that fitted better with WinForms data binding and object data sources.

Just about the same time in june, Martin Fowler retired the MVP pattern for just about the same reasons, and the new data binding friendly pattern is the Supervising Controller. The MVP pattern has actually been split in two, the other pattern being the Passive View.

So if you're still using MVP, be sure to catch up on the new patterns.

Wednesday, June 14, 2006

Model Reference Data = Observer pattern + Reference Data + MVP (Part II)

In part I, I outlined how we use common business process reference data (BPRD) in our WinForms user controls contained in a model-view-presenter application. Before the refactoring, the presenter was used to hold and provide the data, and also had the responsibility of notifying the applicable views that the reference data had changed. This lead to a rather unreadable and unmaintainable design as the set of events and views got bigger

All who have developed a tabbed wizard control know that using a common data object shared between all the tabs is a good design. The refactored design is analogous to this, in addition to employing the Observer pattern. The WinForm user control container module described in Part I is just an advanced, decoupled MVP implementation of a multi-tabbed user interface module.

The refactoring of the module comprises these steps:
1. Extract all common reference data into a new ‘subject’ class as private members.
2. Expose the reference data as public properties.
3. Add event definitions to the applicable properties to notify ‘observers’ of changes to the ‘subject’.
4. Add a property to the presenter to hold an instance of the new BPRD object type.
5. Add a property to the view interface, of the new BPRD object type.
6. Add code in the presenter to create an instance of the reference data (BPRD).
7. Add code in the presenter to set the BPRD property of each of the views.
8. Add event handlers in the presenter and views (observers) to subscribe to applicable notifications from the new BPRD object.

In the new design, all views hold a reference (pointer) to a common, shared instance of the BPRD object. They all change the common object and they all can subscribe to whatever event they need to get notified of. Thus, the presenter need not contain any code to handle BPRD events to then invoke methods on the views. This leads to a much simpler presenter. The views are also somewhat simpler, as the view interface now contains less to implement.

The refactored view interface looks like this:

public
interface IEkspederingView

{

//note how all the get/set events from part I are gone

//property pointing to the current common, shared BPRD

EkspederingFellesData GjeldendeEkspederingFellesData{get; set;}

...

}


The new business process reference data class looks like this:

public class EkspederingFellesData

{

public EventHandler<EventArgs> KontonummerSatt;


public string Kontonummer

{

get { return _kontonummer; }

set

{

_kontonummer = value;

//subject: notify subscribers of change

if (KontonummerSatt != null) this.KontonummerSatt(this, new EventArgs());

}

}

private string _kontonummer;

...

}


The presenter looks like this:

public class EkspederingPresenter

{

public EkspederingPresenter()

{

//create and load the BPRD

_currentReferenceData = new EkspederingFellesData();


//add the current BPRD to all the views

_innbetalingView = new InnbetalingView();

_innbetalingView.GjeldendeEkspederingFellesData = _currentReferenceData;

}


public EkspederingFellesData GjeldendeEkspederingFellesData

{

get { return _currentReferenceData; }

}

private EkspederingFellesData _currentReferenceData;


private InnbetalingView _innbetalingView;

...

}


And finally, one of the views looks like this:

public class InnbetalingView : IEkspederingView

{

public InnbetalingView()

{

//observer: add event handlers to subscribe to notifications to BPRD

this.GjeldendeEkspederingFellesData.KontonummerSatt += new EventHandler<EventArgs>(OnKontonummerSatt);

}


public EkspederingFellesData GjeldendeEkspederingFellesData

{

get { ... }

set{...}

}


public void OnKontonummerSatt(object sender, EventArgs e)

{

//do function X13

}

...

}


The reference data value object is the ‘Subject’ of the Observer pattern, while the ‘Observer’ objects are the presenter and (possibly) the views. Thus, the reference data ‘Subject’ object (don’t get confused) is in fact a ‘Model’ object, although not a domain object such as a customer or an order.

If this combination of the observer pattern, the MVP pattern and a reference data object should have a name, I would call it the “Model Reference Data” pattern, the subject/model being the common, shared and observed reference data.

[UPDATE] Martin Fowler has since retired MVP and replaced it with the Supervising Presenter pattern. The new pattern is actually very like our adapted MVP usage, as it fits better with WinForms data binding.

Friday, June 09, 2006

Tell, don’t ask – MVP – Biz Process Reference Data (Part I)

A good programming practice is “tell, don’t ask, which concerns class responsibility (btw, don’t try to apply this practice at home with your wife…). In these postings, I will outline how we use this rule to avoid excessive use of events in a model-view-presenter (MVP) WinForms module to get at the reference data of the current business process.

Reference data
is used in the model to annotate the business transaction work item data with extra information such as the customer number, account number, zip code, country, etc.

I have successfully used “tell, don’t ask” to refactor code that used events to get business process reference data (BPRD) values stored in a WinForms presenter control, to use a common, shared ‘current reference data’ object to contain and control the reference data. This also leads to a better design that follows the “single responsibility principle
and leads to better cohesion / separation of concerns in the presenter and the current-reference-data classes.

The WinForms module consists of a container user control that hosts a set of child user controls inside a tab control. In fact, one of the child controls is it self a tabbed container with further child controls. Before the refactoring, the reference data was stored in the presenter, much like the view-state data of the presentation-model
. Note that we use an adapted version of MVP due to our extensive use of WinForms databinding. Note also that we by constraint cannot use CAB in this project.

See J Aron Farr’s post at JadeTower
on ‘MVC, MVP, Presenter Model for more info about the different presentation logic patterns.

The old code contained several events for getting and setting the reference data defined in the view interface (one event pair per value). The child user controls (the views) would raise an event that the presenter would handle to provide the requested reference data value. Thus, a view would need ask for the data to get it and beg for the presenter to change it. After all, raising an event is not dictating (tell) something to happen, it is more like pleading (ask). This coding style started out small with just one value to get.

An example view interface (sorry about the Norwegian naming imposed on me):


Public Interface IKasseEkspedisjonView

#Region "Events"
Event KontonummerHent as EventHandler(Of GenericEventArgs(Of String))
Event KontonummerSett as EventHandler(Of GenericEventArgs(Of String))

Event BuntnummerHent as EventHandler(Of GenericEventArgs(Of Integer))
Event BuntnummerSett as EventHandler(Of GenericEventArgs(Of Integer))

Event EkspedisjonsnummerHent as EventHandler(Of GenericEventArgs(Of Integer))
Event EkspedisjonsnummerSett as EventHandler(Of GenericEventArgs(Of Integer))
#End Region

...


End Interface


The presenter would handle these events both to provide and to update the BPRD values stored in the presenter.


Note that I do not want the views to have a pointer/reference to the presenter. In addition, to keeps things simple, the container is also the presenter. The container naturally has pointers to all the views, added automatically by the Visual Studio designer when adding the child user controls to the tab control.

The events defined in the view interface had to be implemented by all child user controls using BPRD, and even using a user control base class to implement the events only once, it still would lead to a growing number of events and handlers as the number of reference data values increased. Likewise, the presenter would have to add handlers for each new event and for each new child user control added to the container.

It was time for a simpler solution to sharing the business process reference data. The refactoring involves creating a new value object to keep all the reference data and removing all of the get/set events from the view interface in favor of property changed events on the value object. The refactoring will also remove the need for the presenter to subscribe to BPRD events for each view instance.

Stay tuned for details about the refactored business-process-reference-data object in the upcoming part II.


[UPDATE] Martin Fowler has since retired MVP and replaced it with the Supervising Presenter pattern. The new pattern is actually very like our adapted MVP usage, as it fits better with WinForms data binding.