分享

Model View Presenter (MVP) design pattern close look - Part 1 (Supervising Controller) - VusCode - C

 xiao huan 2008-08-29

Model View Presenter (MVP) design pattern close look - Part 1 (Supervising Controller)

 

Usually, usage of design patterns is associated with code executing in the lower tiers, but design patterns can be very usefully used in solving problems of the UI world too.

Two of my architecture blog posts are covering the examples related to patterns usage in solving the Web problems:

Although young,Model View Presenter pattern has been retired and two variations from original MVP pattern came up from it.

This little series of blog posts would cover in depth architecture and implementation of the both MVP design pattern successors: Passive View and Supervising Controller. All of the blog posts would use the same use case and the same Model domain entities, so the downsides and upsides of the different approaches can be easier notable.  

What is Model View Presenter?

If you are new to Model View Presenter pattern at all, you should check the Model View Presenter entry level post which explains the most important concepts of wiring the presenter and view in step by step manner. Once you would chew that, come back to this post and continue reading :)

  As we can see from this diagram:

  • View contains the Presenter instance (view "knows" presenter)
  • Presenter is the only class knowing how to reach to model and retrieve the data needed for performing business logic
  • Presenter talks to the View through the view interface (abstracted representation of the View without UI specific attributes)
  • View doesn‘t know nothing about the Model

View responsibility is to show the data provided by presenter, and purpose of the presenter is to reach to model, retrieve the needed data, performs required processing and returns the UI prepared data to the view.

The biggest difference between the Model View Controller (MVC) and Model View Presenter (MVP) pattern is in the fact that (in MVC) View is aware of model too.

I personally prefer more the MVP pattern because it leads to easier testability and decouples completely UI from the data sources, making it by that very testable.

Use case

Today‘s use case would be that we got request to build a simple web page which would allow us searching for a desired user and presenting its data retrieved from DB  on web page. User data which should be resented are: first name, last name and collection of his addresses. Web page is supposed to provide a way of informing the user when there is no user to be retrieved with a given use name.

UI interface should offer the possibility of editing the first name and last name data, but presenter should persist the data only if there would be some changes. In case UI didn‘t modify data retrieved from DB, presenter shouldn‘t make DB call at all.

The implementation design should allow testing the UI layer without using UI specific testing frameworks, preferably with most of the code logic extracted from a page level

Model

Both patterns today (passive view and supervising controller) would share the same model in their examples. Model for this use case would be represented with a component called UserRepositry which static class diagram looks something like this:

image

The User class is a domain data carrier class which has 4 properties.

Three of them are required to be presented to web page user:Name, Surname and Address where the Address is collection of UserAddress objects with City and Street properties.

Credit card number is user attribute which we don‘t want to present to web page user. It represents the example of domain attribute not required by UI.

The IUserService interface defines methods:

  • GetUser which for a given user name returns user object populated with appropriate retrieved data.
  • SaveUser which persist given user object

UserServiceStub is just a stub class stubbing the operation of user data retrieval from a database. I‘m aware that  in unit tests I could use dynamic mock objects but for purpose of this blog post treat UserServiceStub as a class retrieving the data from a DB. The class itself implements IUserService like this:

   1: public class UserServiceStub:IUserService
   2: {
   3:     #region IUserService Members
   4:  
   5:     public User GetUser(string userName)
   6:     {
   7:         if (userName == "Nikola")
   8:         {
   9:             User newUser = new User();
  10:         
  11:             newUser.Name = "Nikola";
  12:             newUser.Surname = "Malovic";
  13:             newUser.Addresses.Add(new UserAddress("Vaclavske Namesti 11", "Prague, CZE"));
  14:             newUser.Addresses.Add(new UserAddress("5 Clock Tower", "Maynard, MA, USA"));
  15:             return newUser;
  16:         }
  17:         return null;
  18:     }
  19:  
  20:     public void SaveUser(User user)
  21:     {
  22:         // perform saving to DB
  23:     }
  24:  
  25:     #endregion
  26: }

In case that the given user name would be Nikola, the method would return user object with first name, last name and two addresses. In case of different user name the method would return null. Save method is just stubbing the functionality which saves the user data into the database

View markup

Both examples would share the same page html markup code definition which would look something like this:

   1: <div>
   2:     Enter desired user name:<asp:TextBox ID="SearchCriteriaTextBox" runat="server"></asp:TextBox>
   3:     <asp:Button ID="SearchButton" runat="server"  Text="Find user" OnClick="OnUserSearchButtonClicked" />
   4:     <br />
   5:     <asp:Label ID="ResultLabel" runat="server" />
   6:     <br />
   7:     <div id="resultPanel" runat="server" visible="false">
   8:         <asp:Label ID="FirstNameLabel" runat="server" text="First name" />
   9:         <asp:TextBox ID="FirstNameTextBox" runat="server" OnTextChanged="FirstName_TextChanged"  />        
  10:         <br />
  11:         <asp:Label ID="LastNameLabel" runat="server" text="Last name" />
  12:         <asp:TextBox  ID="LastNameTextBox" runat="server" OnTextChanged="LastName_TextChanged"/>        
  13:         <br />
  14:         <asp:Repeater ID="AdressesRepeater" runat="server">
  15:             <HeaderTemplate>
  16:                 <strong>Adresses</strong><br />
  17:             </HeaderTemplate>
  18:             <ItemTemplate>
  19:                 <%# Container.DataItem.ToString() %> <br />
  20:             </ItemTemplate>
  21:         </asp:Repeater>
  22:         <br />
  23:         <asp:Button ID="SaveButton" runat="server" Text="Save changes" OnClick="OnUserSaveButtonClicked" />
  24:     </div>
  25: </div>

On the top of the web form there is a SearchCriteriaTextBox  where the user would enter the user name of the user which data he wants to see. Once he would enter that, he would click on a SearchButton which click event would  be handled in OnSearchUser_Click event handler

ResultLabel purpose is to show the informative messages to web page user if there is no user with a given user name etc..

User data successfully retrieved  would be presented inside of the result pannel:

  • first name in FirstNameTextBox,
  • last name in LastNameTestBox
  • user addresses in repeateer called AddressRepeater

Inside of the result panel there is also Save changes button which click instantiates user persisting action

The page in design mode would look something like this:

image

Supervising controller

Supervising controller is a type of MVP design pattern based on a concept of data context binding where the view is implementing some "binding" logic on a object received from presenter which contains certain group of business data needed for the whole view. The view is therefore performing some functionality on it‘s own.

The biggest advantage of supervising controller is in the fact that presenter is not aware of the view internals, which allows reuse of one presenter with multiple views using the same context data source but with different UI elements. Also presenters are in general much simpler comparing with the Passive view presenters which need to take care about every single aspect of the view.

View interface

image

Interface contains next abstract representations:

  • label showing error messages is abstracted to StatusMessage - string setter property.
    Labels and in general other "read only" controls should be represented without getter, because user can not modify their value and therefore presenter is not interested in reading their value 
  • Find user button is abstracted to Search event represented with generic EventHandler<SearchEventArgs> delegate signature.
    SearchEventArgs is just an EventArgs using the additional SearchCriteria string property to carry the lookup criteria from the view to presenter on decoupled manner. Something like this:
    image
  • Save button is abstracted to a Save event argument with a default event argument signature
  • Last part of the view interface is a ContextData property. The purpose of this property is to behave as a "view data source".
    The communication between the presenter and the view is been done through that context data object
    Presenter is responsible to set the context data object value with values retrieved from the model and the view is supposed to update itself from that context data object value. View is also responsible to update appropriate context data properties with the values of the UI specific elements. Presenter then detects all the changes and updates the model with them.

View DTO (data transfer object)

Purpose of the DTO (Data transfer object) is to be information mule, without any logic inside which has a collection of properties in which data is carried in encapsulated manner. DTO pattern is essential one in implementing SOA systems, but I‘ll skip that aspect for now.

The biggest question related to View DTO is when we should use it at all? The downside of DTO is that we have to map domain object to the DTO which introduces one more level of indirection  ("There is no problem which can not be solved with one more level abstraction") and additional coding.

In our little example the IUserService.Get method returns the User object so why not pass that object to the view and skip the mapping hustle?
There are couple of cases which could be signals that the DTO mapping is required:

  1. User can be very heavy object which wouldn‘t be serialization optimal if we have distributed system scenarios
  2. In case of Active Record pattern based design it could expose some methods to the UI developer (user.Save()) which could confuse him or tempt him to make view code logic heavy avoiding presenter
  3. We need to present only a subset of the information domain object carries (in this example we don‘t need credit card information on UI level)
  4. We need to translate some of the strongly typed data to more "UI friendly" type of data. (In the example UserAddress type collection data would be translated to a collection of the strings so the repeater could perform easier data binding)

So, in this example UserDTO looking like this:

image

As we can see the DTO object has first name and last name string properties and collection of string addresses. There  is no credit card number property, because it is not required to be presented in UI.
IsDirty getter property is the property which value would be set to true if some of the property values would change. That value would be used for determining if the user data should be persisted or not

Data mapping

Data mapper design pattern default interpretation includes into the set of data mapper responsibilities beside mapping the data also the data retrieval activities. Although, I agree  that is the official and correct implementation, I don‘t like that small functional add on because it breaks the separation of concern principles. IMHO, purpose of the data mapper is just transformation of the already loaded data. Mapper should only know "how to map".

So, our little example therefore contains a DataMapper static helper class which class diagram looks like this:

image

We have overloaded Translate method which performs appropriate data translation for a given method parameter type

Let‘s take a quick look at the implementation of the first one

   1: public static UserDTO Translate(User user)
   2: {
   3:     UserDTO userDTO = new UserDTO();
   4:     userDTO.FirstName = user.Name;
   5:     userDTO.LastName = user.Surname;
   6:     userDTO.Addresses = MapUserAddress(user.Addresses);
   7:     return userDTO;
   8: }
   9:  
  10: private static IEnumerable<string> MapUserAddress(IEnumerable<UserAddress> adresses)
  11: {
  12:     foreach (UserAddress address in adresses)
  13:     {
  14:         yield return string.Format("{0},{1}", address.Street, address.City);
  15:     }
  16: }

This translate method accepts the User type instance as parameter.
In line 3, method creates the UserDTO instance and then maps the values of User instance properties to appropriate properties of UserDTO
In line 6, the method calls the user address collection mapping method which transform the collection of the UserAddresses into the IEnumerable<string> utilizing the yield c#

View implementation

Presenter - View wire up

The pattern used for establishing wire up between the view and presenter is dependency injection - constructor type.

The  key concepts of this wire up are:

  • presenter has a constructor accepting parameter of view interface type
  • view implements the view interface
  • view in page load constructs a presenter instance and throw himself to the presenter
  • presenter has a pointer then to the view, but the pointer type is view interface so presenter is not knowing nothing about the UI specific aspects of the view he‘s been injected
  • view keeps created instance of the pointer in the page field to support presenter method invocation in case of specific UI events

In code that would look like this:

   1: private UserDetailsPresenter _presenter;
   2:  
   3: protected void Page_Load(object sender, EventArgs e)
   4: {
   5:     // injecting the view into the presenter
   6:     _presenter = new UserDetailsPresenter(this);
   7: }

View interface implementation

The view is a web page implementing the view interface and wiring and mapping  the UI elements properties to the certain view interface members, something like this:

   1: #region IUserDetailsView Members
   2:  
   3: public UserDTO ContextData
   4: {
   5:     get { return Session["Context"] as UserDTO; }
   6:     set
   7:     {
   8:         Session["Context"] = value;
   9:         OnContextDataBound();
  10:     }
  11: }
  12:  
  13: public string StatusMessage
  14: {
  15:     set { ResultLabel.Text = value; }
  16: }
  17:  
  18: public event EventHandler<SearchEventArgs> Search;
  19: public event EventHandler<EventArgs> Save;
  20:  
  21: #endregion

In line 13, we implement the IView.StatusMessage property on a way that setting that property would end with setting the Web.UI.Label text property with the same value.

ContextData property implementation in this example stores a interface property value to a session variable (to prevent it from post backs) and calls the OnContextDataBound method where View would update itself with new context data, on a way similar to this:

   1: private void OnContextDataBound()
   2: {
   3:     FirstNameTextBox.Text = ContextData.FirstName;
   4:     LastNameTextBox.Text = ContextData.LastName;
   5:     AdressesRepeater.DataSource = ContextData.Addresses;
   6:     AdressesRepeater.DataBind();
   7:     resultPanel.Visible = true;
   8: }

So, in lines 3 and 4 we just set the text boxes text property with context object data values.
In line 5 and 6 code performs data binding to the collection of addresses context data
At the end, result panel is been presented to user.

View interface events  are implemented in lines 18 and 19, and they have just trivial event handlers to raise those events, something like this:

   1: protected void OnUserSearchButtonClicked(object sender, EventArgs e)
   2: {
   3:     if (Search != null)
   4:         Search(this, new SearchEventArgs(SearchCriteriaTextBox.Text));
   5: }
   6:  
   7: protected void OnUserSaveButtonClicked(object sender, EventArgs e)
   8: {
   9:     if (Save != null)
  10:         Save(this, new EventArgs());
  11: }

View routing logic is implementing the functionality of reflecting the UI specific changes to the appropriate context data object members.
Implementation could be trivial as the next code :

   1: protected void FirstName_TextChanged(object sender, EventArgs e)
   2: {
   3:     ContextData.FirstName = FirstNameTextBox.Text;
   4: }
   5:  
   6: protected void LastName_TextChanged(object sender, EventArgs e)
   7: {
   8:     ContextData.LastName = LastNameTextBox.Text;
   9: }

At the end of the view code implementation, the view needs to wire up itself with presenter (MVP diagram shows that view contains the instance of presenter) and we can do that by doing something like this:

   1: UserDetailsPresenter _userDetailsPresenter;
   2: protected void Page_Load(object sender, EventArgs e)
   3: {
   4:     // injecting the view into the presenter
   5:     _userDetailsPresenter=new UserDetailsPresenter(this);
   6: }

As we can see in line 5, the view constructs presenter instance and throws itself to the presenter constructor parameter so the presenter could have a pointer to the  view interface defined members.

Presenter

Class diagram of the presenter looks like this:

image

As we can see from the diagram, presenter has two public constructors and two private methods handling the appropriate view interface events. Presenter has also two fields which hold the presenter pointers to the view and service layers.

Presenter initialization

All the business logic of controlling,presenting and updating model and interaction with view should be encapsulated in the presenter. In this example Presenter is getting pointer to the view interface and model services through the utilization of constructor type of dependency injection design pattern.

   1: public UserDetailsPresenter(IUserDetailsView view, IUserService userService)
   2:  {
   3:      _view = view;
   4:      _userService = userService;
   5:      _view.Search += (OnUserSearch);
   6:      _view.Save += new EventHandler<EventArgs>(OnUserSave);
   7:  }
   8:  
   9:  public UserDetailsPresenter(IUserDetailsView view)
  10:      : this(view, new UserServiceStub())
  11:  {
  12:  }

I won‘t detail here too much about how DI works (if you need that, check out the Model View Presenter post) but summarized whatever the classes implementing the view and service interfaces would be, its pointers would be stored inside of the presenter object and the presenter would use them without having a knowledge what are the exact classes implementing the interfaces.

That‘s how the presenter would know only about the view interface (which abstracts the UI concepts) and not the view.

In line 5 and 6 we can see that the presenter is subscribing to the view save and search events.

The purpose of the first constructor is primary to enable appropriate unit testing of the presenter, but it won‘t be used in real world use cases. The reason why is like that lies in the fact that the view should be ignorant about the model in MVP pattern, so the view shouldn‘t know which model service class should inject to the presenter.

Second constructor is the one used by a view and we can se that it is enough for a view to pass a reference to itself  to a constructor and the Poor man dependency injection would occur in this(view, new UserServiceStub())  so the UserServiceStub instance pointer would be by default injected into the presenter.

One additional benefit of having those two constructors could be that the first one could be used (together with mocking) in implementing unit tests and the second one could be called in integration tests (where we need tests to talk with real, non mocked model representations like database for e.g.)

Presenter implementing view required logic

We have two methods in presenter which perform certain actions for a view, when view requests them.

We saw that to request something from a presenter the view would raise an event and due to the fact that presenter is in its constructor subscribing to the those events, when the view would send event presenter private method would be executed

NOTE: This is standard ‘Fowler like‘ event based implementation of the view presenter  communication. The other (simpler) way how this could be implemented is that presenter could define as public methods implementing the logic required by a view and the view could directlly call them with something like: _presenter.OnUserSave() without having the need for defining events and related code concepts. The price of simplicity is that the presenter  broke his encapsulation from the view and that the view implementation is now tied to the specific presenter implementation. For a lot of people, this is fair trade off, so in my personal development I am implementing it like that.
The only reason why I am presenting it using events is to avoid comments that is "not by the book" and "too coupled" :)

OnUserSave method implementation

   1: private void OnUserSave(object sender, EventArgs e)
   2: {
   3:     if (_view.ContextData.IsDirty)
   4:     {
   5:         User user=new User();
   6:  
   7:         User domainUser = DataMapper.Translate(_view.ContextData);
   8:         _userService.SaveUser(domainUser);
   9:     }
  10: }
  11:  

In line 3, we see the presenter reads the context object IsDirty property value to determine if data is changed from the loaded data and if persisting is needed and if the IsDirty is true, in line 7 the DTO objects is been mapped (translated) to the domain type and the service persist method is called after

OnUserSearch method implementation

   1: private void OnUserSearch(object sender, SearchEventArgs e)
   2: {
   3:     if (string.IsNullOrEmpty(e.SearchCriteria))
   4:     {
   5:         _view.StatusMessage = "User name can not be null";
   6:         return;
   7:     }
   8:  
   9:     User user = _userService.GetUser(e.SearchCriteria);
  10:     if (user == null)
  11:     {
  12:         _view.StatusMessage = String.Format(
  13:             "There‘s no user found for user name:{0}", e.SearchCriteria);
  14:         return;
  15:     }
  16:  
  17:     UserDTO userDTO = DataMapper.Translate(user);
  18:     _view.ContextData = userDTO;
  19: }

In line 3 we examine the value of the event parameter caring the lookup user name data and in case of null or empty value in line 5 we set the view interface StatusMessage property value (which would cause on view level setting up the status message) to appropriate error message.

In line 9, presenter is accessing the Model through the UserService class GetUser method and in case of not existing user in line 12 sets the view interface message property to appropriate error message.

Finally, if a user is been successfully retrieved from a user service, presenter performs in line 17 mapping (translation) of the domain user data to the DTO type of user data which is then set to the interface view ContextData property (which would cause on view level OnDataBound method execution) 

Summary

Supervising Controller pattern is a UI design pattern which takes most complex responsibilities from the back of the web pages but still leaving them fair amount of autonomy. That makes it more suitable for implementing more complex UI pages/controls because having oversized presenter and undersized view can sometimes cause in teams overwhelming of developers working on presenter and leaves underused the one working on the view

Source code of today‘s example can be found here:  Supervising Controller source code

What is next?

Next part of the MVP close look post series would continue the MVP saga dissecting the Passive View variation of MVP pattern. Some of the classes and concepts presented in this post would be just referenced in i, so it would definitely be much shorter :)

Quote of the day:
Another such victory, and we are undone. - Pyrrhus

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多